lib/sqed/boundary_finder.rb in sqed-0.1.5 vs lib/sqed/boundary_finder.rb in sqed-0.1.6
- old
+ new
@@ -1,38 +1,83 @@
# Sqed Boundary Finders find boundaries on images and return co-ordinates of those boundaries. They do not
# return derivative images. Finders operate on cropped images, i.e. only the "stage".
#
class Sqed::BoundaryFinder
+
+ THUMB_SIZE = 100
+
# the passed image
attr_reader :img
# a symbol from SqedConfig::LAYOUTS
attr_reader :layout
# A Sqed::Boundaries instance, stores the coordinates of all of the layout sections
attr_reader :boundaries
- def initialize(image: image, layout: layout)
+ # Whether to compress the original image to a thumbnail when finding boundaries
+ attr_reader :use_thumbnail
+
+ # when we compute using a derived thumbnail we temporarily store the full size image here
+ attr_reader :original_image
+
+ def initialize(image: image, layout: layout, use_thumbnail: true)
raise 'No layout provided.' if layout.nil?
raise 'No image provided.' if image.nil? || image.class.name != 'Magick::Image'
+ @use_thumbnail = use_thumbnail
+
@layout = layout
@img = image
true
end
# Returns a Sqed::Boundaries instance initialized to the number of sections in the passed layout.
def boundaries
@boundaries ||= Sqed::Boundaries.new(@layout)
end
+ def longest_thumbnail_axis
+ img.columns > img.rows ? :width : :height
+ end
+ def thumbnail_height
+ if longest_thumbnail_axis == :height
+ THUMB_SIZE
+ else
+ (img.rows.to_f * (THUMB_SIZE.to_f / img.columns.to_f)).round.to_i
+ end
+ end
+
+ def thumbnail_width
+ if longest_thumbnail_axis == :width
+ THUMB_SIZE
+ else
+ (img.columns.to_f * (THUMB_SIZE.to_f / img.rows.to_f)).round.to_i
+ end
+ end
+
+ # see https://rmagick.github.io/image3.html#thumbnail
+ def thumbnail
+ img.thumbnail(thumbnail_width, thumbnail_height)
+ end
+
+ def width_factor
+ img.columns.to_f / thumbnail_width.to_f
+ end
+
+ def height_factor
+ img.rows.to_f / thumbnail_height.to_f
+ end
+
+ def zoom_boundaries
+ boundaries.zoom(width_factor, height_factor )
+ end
+
# return [Integer, nil]
# sample more with small images, less with large images
# we want to return larger numbers (= faster sampling)
- #
- #
def self.get_subdivision_size(image_width)
case image_width
when nil
nil
when 0..140
@@ -67,10 +112,11 @@
#
# @param scan
# (:rows|:columns), :rows finds vertical borders, :columns finds horizontal borders
#
def self.color_boundary_finder(image: image, sample_subdivision_size: nil, sample_cutoff_factor: nil, scan: :rows, boundary_color: :green)
+
image_width = image.send(scan)
sample_subdivision_size = get_subdivision_size(image_width) if sample_subdivision_size.nil?
samples_to_take = (image_width / sample_subdivision_size).to_i - 1
border_hits = {}
@@ -103,10 +149,12 @@
return nil if border_hits.length < 2
if sample_cutoff_factor.nil?
cutoff = max_difference(border_hits.values)
+
+ cutoff = border_hits.values.first - 1 if cutoff == 0 # difference of two identical things is 0
else
cutoff = (samples_to_take * sample_cutoff_factor).to_i
end
frequency_stats(border_hits, cutoff)
@@ -130,20 +178,32 @@
end
# return [Array]
# the median position of all (pixel) positions that have a count greater than the cutoff
def self.frequency_stats(frequency_hash, sample_cutoff = 0)
+
return nil if sample_cutoff.nil? || sample_cutoff < 1
hit_ranges = []
frequency_hash.each do |position, count|
if count >= sample_cutoff
hit_ranges.push(position)
end
end
- return nil if hit_ranges.size < 3
+ case hit_ranges.size
+ when 1
+ c = hit_ranges[0]
+ hit_ranges = [c - 1, c, c + 1]
+ when 2
+ hit_ranges.sort!
+ c1 = hit_ranges[0]
+ c2 = hit_ranges[1]
+ hit_ranges = [c1, c2, c2 + (c2 - c1)]
+ when 0
+ return nil
+ end
# we have to sort because the keys (positions) we examined came unordered from a hash originally
hit_ranges.sort!
# return the position exactly in the middle of the array
@@ -172,5 +232,6 @@
def self.derivative(array)
(0..array.length-2).map { |i| array[i+1] - array[i] }
end
end
+