Class: Panomosity::Pair

Inherits:
Object
  • Object
show all
Extended by:
Utils
Includes:
Utils
Defined in:
lib/panomosity/pair.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

calculate_average, calculate_average_and_std, remove_outliers

Constructor Details

#initialize(pair, control_points: [], type: nil) ⇒ Pair

Returns a new instance of Pair.



156
157
158
159
160
161
162
163
# File 'lib/panomosity/pair.rb', line 156

def initialize(pair, control_points: [], type: nil)
  @pair = pair
  @control_points = control_points
  @neighborhoods = []
  @generalized_neighborhoods = []
  @similar_neighborhoods = []
  @type = type
end

Class Attribute Details

.loggerObject

Returns the value of attribute logger.



11
12
13
# File 'lib/panomosity/pair.rb', line 11

def logger
  @logger
end

.panoramaObject

Returns the value of attribute panorama.



11
12
13
# File 'lib/panomosity/pair.rb', line 11

def panorama
  @panorama
end

Instance Attribute Details

#control_pointsObject

Returns the value of attribute control_points.



8
9
10
# File 'lib/panomosity/pair.rb', line 8

def control_points
  @control_points
end

#generalized_neighborhoodsObject

Returns the value of attribute generalized_neighborhoods.



8
9
10
# File 'lib/panomosity/pair.rb', line 8

def generalized_neighborhoods
  @generalized_neighborhoods
end

#neighborhoodsObject

Returns the value of attribute neighborhoods.



8
9
10
# File 'lib/panomosity/pair.rb', line 8

def neighborhoods
  @neighborhoods
end

#pairObject

Returns the value of attribute pair.



8
9
10
# File 'lib/panomosity/pair.rb', line 8

def pair
  @pair
end

#similar_neighborhoodsObject

Returns the value of attribute similar_neighborhoods.



8
9
10
# File 'lib/panomosity/pair.rb', line 8

def similar_neighborhoods
  @similar_neighborhoods
end

#typeObject

Returns the value of attribute type.



8
9
10
# File 'lib/panomosity/pair.rb', line 8

def type
  @type
end

Class Method Details

.allObject



21
22
23
# File 'lib/panomosity/pair.rb', line 21

def all
  @pairs
end

.calculate_neighborhood_groupsObject



95
96
97
98
99
# File 'lib/panomosity/pair.rb', line 95

def self.calculate_neighborhood_groups
  NeighborhoodGroup.parse_info(@panorama)
  NeighborhoodGroup.calculate(name: :horizontal, pairs: @horizontal_pairs)
  NeighborhoodGroup.calculate(name: :vertical, pairs: @vertical_pairs)
end

.calculate_neighborhoods(panorama, distance: 30) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/panomosity/pair.rb', line 79

def self.calculate_neighborhoods(panorama, distance: 30)
  create_pairs_from_panorama(panorama)
  @pairs.each { |pair| pair.calculate_neighborhoods(distance: distance) }

  # separate out into horizontal and vertical pairs
  @horizontal_pairs = @pairs.select(&:horizontal?)
  @vertical_pairs = @pairs.select(&:vertical?)

  # sort pairs by average distance first and number of control points descending second
  @horizontal_pairs = @horizontal_pairs.sort_by { |pair| [pair.average_distance, -pair.control_points.count] }
  @vertical_pairs = @vertical_pairs.sort_by { |pair| [pair.average_distance, -pair.control_points.count] }

  log_detailed_neighborhood_info(name: :horizontal, pairs: @horizontal_pairs)
  log_detailed_neighborhood_info(name: :vertical, pairs: @vertical_pairs)
end

.create_pairs_from_panorama(panorama) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/panomosity/pair.rb', line 38

def self.create_pairs_from_panorama(panorama)
  @panorama = panorama
  @logger = @panorama.logger

  images = @panorama.images
  columns = images.map(&:column).uniq.sort
  rows = images.map(&:row).uniq.sort

  @pairs = []
  @horizontal_pairs = []
  @vertical_pairs = []
  
  # horizontal pair creation
  rows.each do |row|
    columns.each do |column|
      next if column == columns.last
      image_1 = images.find { |i| i.row == row && i.column == column }
      image_2 = images.find { |i| i.row == row && i.column == column.next }
      next if @panorama.calibration? && (image_1.nil? || image_2.nil?)
      control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
      pair = Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :horizontal)
      @pairs << pair
      @horizontal_pairs << pair
    end
  end

  # vertical pair creation
  columns.each do |column|
    rows.each do |row|
      next if row == rows.last
      image_1 = images.find { |i| i.column == column && i.row == row }
      image_2 = images.find { |i| i.column == column && i.row == row.next }
      next if @panorama.calibration? && (image_1.nil? || image_2.nil?)
      control_points = @panorama.control_points.select { |cp| [cp.n1, cp.n2].sort == [image_1.id, image_2.id].sort }
      pair = Pair.new([image_1, image_2].sort_by(&:id), control_points: control_points, type: :vertical)
      @pairs << pair
      @vertical_pairs << pair
    end
  end
end

.horizontalObject



13
14
15
# File 'lib/panomosity/pair.rb', line 13

def horizontal
  @horizontal_pairs
end

.infoObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/panomosity/pair.rb', line 116

def self.info
  logger.debug "total number of control points: #{@pairs.map(&:control_points).flatten.count}"
  logger.debug 'displaying horizontal pair info'
  logger.debug "total number of horizontal control points: #{@horizontal_pairs.map(&:control_points).flatten.count}"
  @horizontal_pairs.each do |pair|
    logger.debug pair.info
    logger.debug "total number of control points: #{pair.control_points.count}"
    x_dist, x_std = *calculate_average_and_std(values: pair.control_points.map(&:prx))
    y_dist, y_std = *calculate_average_and_std(values: pair.control_points.map(&:pry))
    dist, std = *calculate_average_and_std(values: pair.control_points.map(&:prdist))
    logger.debug "control points: x_dist,x_std: #{x_dist},#{x_std} | y_dist,y_std: #{y_dist},#{y_std} | dist,std: #{dist},#{std}"
    logger.debug "total number of neighborhoods: #{pair.neighborhoods.count}"
    logger.debug "total number single cp neighborhoods: #{pair.neighborhoods.select{|n| n.control_points.count == 1}.count}"
    logger.debug "total number generated control points: #{pair.control_points.select(&:generated?).count}"
    pair.neighborhoods.each do |neighborhood|
      logger.debug neighborhood.info
      logger.debug "neighborhood: distance,pair_distance: #{neighborhood.distance},#{neighborhood.pair_distance} | total number of control points: #{neighborhood.control_points.count}"
      logger.debug "neighborhood: center prdist: #{neighborhood.center.prdist} | total number of control points within std: #{neighborhood.control_points_within_std.count}"
    end
  end
  logger.debug 'displaying vertical pair info'
  logger.debug "total number of vertical control points: #{@vertical_pairs.map(&:control_points).flatten.count}"
  @vertical_pairs.each do |pair|
    logger.debug pair.info
    logger.debug "total number of control points: #{pair.control_points.count}"
    x_dist, x_std = *calculate_average_and_std(values: pair.control_points.map(&:prx))
    y_dist, y_std = *calculate_average_and_std(values: pair.control_points.map(&:pry))
    dist, std = *calculate_average_and_std(values: pair.control_points.map(&:prdist))
    logger.debug "control points: x_dist,x_std: #{x_dist},#{x_std} | y_dist,y_std: #{y_dist},#{y_std} | dist,std: #{dist},#{std}"
    logger.debug "total number of neighborhoods: #{pair.neighborhoods.count}"
    logger.debug "total number single cp neighborhoods: #{pair.neighborhoods.select{|n| n.control_points.count == 1}.count}"
    logger.debug "total number generated control points: #{pair.control_points.select(&:generated?).count}"
    pair.neighborhoods.each do |neighborhood|
      logger.debug neighborhood.info
      logger.debug "neighborhood: distance,pair_distance: #{neighborhood.distance},#{neighborhood.pair_distance} | total number of control points: #{neighborhood.control_points.count}"
      logger.debug "neighborhood: center prdist: #{neighborhood.center.prdist} | total number of control points within std: #{neighborhood.control_points_within_std.count}"
    end
  end
end

.log_detailed_neighborhood_info(name: :horizontal, pairs: []) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/panomosity/pair.rb', line 101

def self.log_detailed_neighborhood_info(name: :horizontal, pairs: [])
  return unless @panorama.options[:verbosity] > 1
  logger.debug "showing #{name} pair information"
  pair = pairs.max_by { |p| p.control_points_of_best_neighborhood.count }
  logger.debug "best #{name} pair #{pair.to_s} found #{pair.control_points_of_best_neighborhood.count} control points"
  pairs.each do |p|
    logger.debug "#{name} pair #{p.to_s} found #{p.control_points_of_best_neighborhood.count} control points"
    p.neighborhoods.each do |n|
      logger.debug "neighborhood centered at #{n.center.x1},#{n.center.y1}: #{n.control_points.count} control points"
      logger.debug "neighborhood centered at #{n.center.x1},#{n.center.y1}: prdist #{n.prdist_avg},#{n.prdist_std} prx #{n.prx_avg},#{n.prx_std} pry #{n.pry_avg},#{n.pry_std}"
      n.control_points.each { |point| logger.debug point.detailed_info }
    end
  end
end

.select_control_points_with_regional_distance_similaritiesObject



25
26
27
# File 'lib/panomosity/pair.rb', line 25

def select_control_points_with_regional_distance_similarities
  @pairs.map(&:select_control_points_with_regional_distance_similarities).flatten.uniq(&:raw)
end

.unconnectedObject



29
30
31
# File 'lib/panomosity/pair.rb', line 29

def unconnected
  @pairs.select(&:unconnected?).sort_by(&:to_s)
end

.verticalObject



17
18
19
# File 'lib/panomosity/pair.rb', line 17

def vertical
  @vertical_pairs
end

.without_enough_control_points(ignore_connected: false) ⇒ Object



33
34
35
# File 'lib/panomosity/pair.rb', line 33

def without_enough_control_points(ignore_connected: false)
  @pairs.select { |pair| (ignore_connected || pair.connected?) && pair.control_points.count < 3 }
end

Instance Method Details

#==(other) ⇒ Object



169
170
171
# File 'lib/panomosity/pair.rb', line 169

def ==(other)
  to_s == other.to_s
end

#attributesObject



263
264
265
266
267
268
269
270
271
272
273
# File 'lib/panomosity/pair.rb', line 263

def attributes
  x_avg, x_std = *calculate_average_and_std(values: control_points.map(&:px), ignore_empty: true)
  y_avg, y_std = *calculate_average_and_std(values: control_points.map(&:py), ignore_empty: true)
  dist_avg, dist_std = *calculate_average_and_std(values: control_points.map(&:pdist), ignore_empty: true)
  i1 = control_points.first.n1
  i2 = control_points.first.n2
  {
    id: [i1, i2], n: i1, N: i2, count: control_points.count, type: type,
    x_avg: x_avg, x_std: x_std, y_avg: y_avg, y_std: y_std, dist_avg: dist_avg, dist_std: dist_std
  }
end

#average_distanceObject



201
202
203
# File 'lib/panomosity/pair.rb', line 201

def average_distance
  calculate_average(values: control_points.map(&:prdist), ignore_empty: true)
end

#best_neighborhoodObject



255
256
257
# File 'lib/panomosity/pair.rb', line 255

def best_neighborhood
  @best_neighborhood ||= @neighborhoods.max_by { |n| n.control_points.count }
end

#calculate_neighborhoods(distance: 30) ⇒ Object



205
206
207
208
209
210
# File 'lib/panomosity/pair.rb', line 205

def calculate_neighborhoods(distance: 30)
  @neighborhoods = control_points.map do |cp|
    neighborhood = Neighborhood.new(center: cp, pair: self, distance: distance)
    neighborhood.calculate
  end
end

#connected?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/panomosity/pair.rb', line 185

def connected?
  !unconnected?
end

#control_points_of_best_neighborhoodObject



259
260
261
# File 'lib/panomosity/pair.rb', line 259

def control_points_of_best_neighborhood
  best_neighborhood ? best_neighborhood.control_points : []
end

#first_imageObject



193
194
195
# File 'lib/panomosity/pair.rb', line 193

def first_image
  pair.first
end

#good_control_points_to_keep(count: 3) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/panomosity/pair.rb', line 217

def good_control_points_to_keep(count: 3)
  control_points_to_keep = good_neighborhoods_within_std(count: count).map(&:control_points_within_std).flatten.uniq(&:raw)

  # Keep all our control points if we have less than 10
  if control_points.count >= 10
    ratio = control_points_to_keep.count.to_f / control_points.count
    if ratio < 0.2
      Panomosity.logger.warn "#{to_s} keeping less than 20% (#{(ratio*100).round(4)}%) of #{control_points.count} control points. Reverting and keeping all control points"
      control_points
    else
      control_points_to_keep
    end
  else
    control_points
  end
end

#good_neighborhoods_within_std(count: 3) ⇒ Object

gets all control points for neighborhoods with a good std of distance



213
214
215
# File 'lib/panomosity/pair.rb', line 213

def good_neighborhoods_within_std(count: 3)
  @neighborhoods.select { |n| n.control_points_within_std.count >= count }
end

#horizontal?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/panomosity/pair.rb', line 177

def horizontal?
  @type == :horizontal || (control_points.first && control_points.first.conn_type == :horizontal)
end

#infoObject



173
174
175
# File 'lib/panomosity/pair.rb', line 173

def info
  "#{to_s}(#{type}) image_1 d,e: #{pair.first.d},#{pair.first.e} | image_2 d,e: #{pair.last.d},#{pair.last.e}"
end

#last_imageObject



197
198
199
# File 'lib/panomosity/pair.rb', line 197

def last_image
  pair.last
end

#select_control_points_with_regional_distance_similaritiesObject



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/panomosity/pair.rb', line 239

def select_control_points_with_regional_distance_similarities
  # Keep all our control points if we have less than 10
  if control_points.count >= 10
    ratio = similar_control_points.count.to_f / control_points.count
    if ratio < 0.2
      Panomosity.logger.warn "#{to_s} keeping less than 20% (#{(ratio * 100).round(4)}%) of #{control_points.count} control points. Reverting and keeping all control points"
      control_points
    else
      similar_control_points
    end
  else
    Panomosity.logger.debug "Skipping pair #{to_s} since it has fewer than 10 control points"
    control_points
  end
end

#similar_control_pointsObject

Distance neighborhoods



235
236
237
# File 'lib/panomosity/pair.rb', line 235

def similar_control_points
  @similar_control_points ||= similar_neighborhoods.map(&:reference).map(&:control_points).flatten.uniq(&:raw)
end

#to_sObject



165
166
167
# File 'lib/panomosity/pair.rb', line 165

def to_s
  pair.map(&:id).to_s.gsub(' ', '')
end

#unconnected?Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/panomosity/pair.rb', line 189

def unconnected?
  control_points.empty?
end

#vertical?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/panomosity/pair.rb', line 181

def vertical?
  @type == :vertical || (control_points.first && control_points.first.conn_type == :vertical)
end