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 +