Class: Panomosity::GeneralizedNeighborhood

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

Constant Summary collapse

CONTROL_POINT_ATTRIBUTES =
[:dist_avg, :dist_std, :x_avg, :x_std, :y_avg, :y_std]
ATTRIBUTES =
[:center, :scope, :control_points, :count] + CONTROL_POINT_ATTRIBUTES
DEFAULT_DISTANCE =
100

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(center:, scope:, options: {}) ⇒ GeneralizedNeighborhood

Returns a new instance of GeneralizedNeighborhood.



141
142
143
144
145
146
# File 'lib/panomosity/generalized_neighborhood.rb', line 141

def initialize(center:, scope:, options: {})
  @center = center
  @scope = scope
  @options = options
  @measure = Measure.new
end

Class Attribute Details

.horizontal_neighborhoods_by_similar_neighborhoodObject

Returns the value of attribute horizontal_neighborhoods_by_similar_neighborhood.



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

def horizontal_neighborhoods_by_similar_neighborhood
  @horizontal_neighborhoods_by_similar_neighborhood
end

.horizontal_similar_neighborhoodsObject

Returns the value of attribute horizontal_similar_neighborhoods.



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

def horizontal_similar_neighborhoods
  @horizontal_similar_neighborhoods
end

.neighborhoodsObject

Returns the value of attribute neighborhoods.



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

def neighborhoods
  @neighborhoods
end

.optionsObject

Returns the value of attribute options.



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

def options
  @options
end

.vertical_neighborhoods_by_similar_neighborhoodObject

Returns the value of attribute vertical_neighborhoods_by_similar_neighborhood.



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

def vertical_neighborhoods_by_similar_neighborhood
  @vertical_neighborhoods_by_similar_neighborhood
end

.vertical_similar_neighborhoodsObject

Returns the value of attribute vertical_similar_neighborhoods.



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

def vertical_similar_neighborhoods
  @vertical_similar_neighborhoods
end

Instance Attribute Details

#measureObject (readonly)

Returns the value of attribute measure.



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

def measure
  @measure
end

#optionsObject (readonly)

Returns the value of attribute options.



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

def options
  @options
end

Class Method Details

.attributesObject



23
24
25
# File 'lib/panomosity/generalized_neighborhood.rb', line 23

def attributes
  { name: name }
end

.calculate_all(panorama:, options: {}) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/panomosity/generalized_neighborhood.rb', line 35

def calculate_all(panorama:, options: {})
  @neighborhoods = []
  @options = options

  Pair.create_pairs_from_panorama(panorama)
  calculate_from_pairs
  calculate_from_neighborhoods(type: :horizontal)
  calculate_from_neighborhoods(type: :vertical)

  @neighborhoods
end

.calculate_from_neighborhoods(type:) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/panomosity/generalized_neighborhood.rb', line 64

def calculate_from_neighborhoods(type:)
  count = options[:regional_distance_similarities_count] || 3
  attempts = options[:max_reduction_attempts] || 2

  # calculates similar neighborhoods based on the regional control point distances by pair
  calculate_similar_neighborhoods(type: type, count: count)
  if neighborhoods.empty?
    logger.warn 'total neighborhoods came up empty, neighborhood default count to 2'
    calculate_similar_neighborhoods(type: type, count: 2)
    raise 'still could not find neighborhoods' if neighborhoods.empty?
  end

  std_outlier_reduction(type: type, max_reduction_attempts: attempts)
  calculate_neighborhoods_by_similar_neighborhood(type: type)
end

.calculate_from_pairsObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/panomosity/generalized_neighborhood.rb', line 47

def calculate_from_pairs
  logger.debug 'calculating neighborhoods from pairs'
  Pair.all.each do |pair|
    control_points = pair.control_points.select(&:not_generated?)
    control_points.each do |control_point|
      base_params = { center: control_point, scope: pair, options: options }
      position_params = { measure: { type: :position } }
      neighborhood = calculate_neighborhood(**base_params.merge(position_params))
      pair.generalized_neighborhoods << neighborhood
      distance_params = { measure: { type: :distance, distances: { x1: neighborhood.dist_std } } }
      distance_neighborhood = calculate_neighborhood(**base_params.merge(distance_params))
      neighborhood.reference = distance_neighborhood
      pair.generalized_neighborhoods << distance_neighborhood
    end
  end
end

.calculate_neighborhood(center:, scope:, options:, measure: {}) ⇒ Object



80
81
82
83
84
85
# File 'lib/panomosity/generalized_neighborhood.rb', line 80

def calculate_neighborhood(center:, scope:, options:, measure: {})
  neighborhood = new(center: center, scope: scope, options: options)
  neighborhood.update_measure(measure)
  @neighborhoods << neighborhood.calculate
  neighborhood
end

.calculate_neighborhoods_by_similar_neighborhood(type: :horizontal) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/panomosity/generalized_neighborhood.rb', line 126

def calculate_neighborhoods_by_similar_neighborhood(type: :horizontal)
  instance_variable_set("@#{type}_neighborhoods_by_similar_neighborhood", [])
  similar_neighborhoods(type: type).each do |neighborhood|
    base_params = { center: neighborhood, scope: self, options: options }
    distance_params = { measure: { type: :distance, distances: { x1: neighborhood.dist_std } } }
    neighborhoods_by_similar_neighborhood(type: type) << calculate_neighborhood(**base_params.merge(distance_params))
  end

  neighborhoods_by_similar_neighborhood(type: type).sort_by! { |n| -n.count }
  neighborhoods_by_similar_neighborhood(type: type).max_by(5) { |n| n.count }.each do |n|
    logger.debug "#{n.dist_avg} #{n.dist_std} #{n.count} x#{n.x_avg} y#{n.y_avg}"
  end
end

.calculate_similar_neighborhoods(type: :horizontal, count: 3) ⇒ Object



106
107
108
109
110
111
# File 'lib/panomosity/generalized_neighborhood.rb', line 106

def calculate_similar_neighborhoods(type: :horizontal, count: 3)
  similar_neighborhoods = neighborhoods.select(&:measure_position?).select(&:"#{type}?").select do |neighborhood|
    neighborhood.scope.similar_neighborhoods << neighborhood if neighborhood.reference.count >= count
  end
  self.send(:"#{type}_similar_neighborhoods=", similar_neighborhoods)
end

.horizontalObject



27
28
29
# File 'lib/panomosity/generalized_neighborhood.rb', line 27

def horizontal
  horizontal_neighborhoods_by_similar_neighborhood
end

.loggerObject



19
20
21
# File 'lib/panomosity/generalized_neighborhood.rb', line 19

def logger
  @logger ||= Panomosity.logger
end

.neighborhoods_by_similar_neighborhood(type: :horizontal) ⇒ Object



102
103
104
# File 'lib/panomosity/generalized_neighborhood.rb', line 102

def neighborhoods_by_similar_neighborhood(type: :horizontal)
  type == :horizontal ? @horizontal_neighborhoods_by_similar_neighborhood : @vertical_neighborhoods_by_similar_neighborhood
end

.similar_neighborhoods(type: :horizontal) ⇒ Object



87
88
89
# File 'lib/panomosity/generalized_neighborhood.rb', line 87

def similar_neighborhoods(type: :horizontal)
  type == :horizontal ? @horizontal_similar_neighborhoods : @vertical_similar_neighborhoods
end

.similar_neighborhoods!(type: :horizontal) ⇒ Object



91
92
93
94
95
96
97
98
99
100
# File 'lib/panomosity/generalized_neighborhood.rb', line 91

def similar_neighborhoods!(type: :horizontal)
  neighborhoods = similar_neighborhoods(type: type)

  if neighborhoods.nil? || neighborhoods.empty?
    error = "No similar #{type} neighborhoods found"
    raise NoSimilarNeighborhoodsError, error
  else
    neighborhoods
  end
end

.std_outlier_reduction(type: :horizontal, max_reduction_attempts: 2, reduction_attempts: 0) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/panomosity/generalized_neighborhood.rb', line 113

def std_outlier_reduction(type: :horizontal, max_reduction_attempts: 2, reduction_attempts: 0)
  return if reduction_attempts >= max_reduction_attempts

  logger.debug "twice reducing #{type} neighborhood std outliers"

  neighborhoods = similar_neighborhoods!(type: type)
  std_dist_of_neighborhoods = neighborhoods.map(&:dist_std)
  avg, std = *calculate_average_and_std(values: std_dist_of_neighborhoods)

  similar_neighborhoods!(type: type).select! { |n| (avg - n.dist_std).abs <= std }
  std_outlier_reduction(type: type, max_reduction_attempts: max_reduction_attempts, reduction_attempts: reduction_attempts + 1)
end

.verticalObject



31
32
33
# File 'lib/panomosity/generalized_neighborhood.rb', line 31

def vertical
  vertical_neighborhoods_by_similar_neighborhood
end

Instance Method Details

#attributesObject



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/panomosity/generalized_neighborhood.rb', line 258

def attributes
  attributes = CONTROL_POINT_ATTRIBUTES.reduce({}) { |h, k| h.merge!({ k => self.send(k) }) }
  measure_attributes = measure.attributes.reduce({}) do |hash, (key, value)|
    hash["measure_#{key}"] = value
    hash
  end
  if pair_scope?
    center_id = center[:id]
    scope_id = [scope.control_points.first.n1, scope.control_points.first.n2]
    scope_name = 'pair'
  else
    center_id = center.id
    scope_id = nil
    scope_name = 'neighborhood'
  end
  attributes.merge!(measure_attributes)
  attributes.merge!(id: id, center: center_id, scope_id: scope_id, scope_name: scope_name, type: type, control_points: control_points.map{|c| c[:id]})
  attributes
end

#calculateObject



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/panomosity/generalized_neighborhood.rb', line 235

def calculate
  if pair_scope?
    elements = scope.control_points.select(&:not_generated?)

    @control_points = if measure_position?
      elements.select { |cp| measure.includes?(cp.x1, cp.y1) }
    else
      elements.select { |cp| measure.includes?(cp.pdist) }
    end
  else
    @neighborhoods = scope.similar_neighborhoods(type: center.type).select { |n| measure.includes?(n.dist_avg) }
    distance_neighborhoods = @neighborhoods.map(&:reference)
    @control_points = distance_neighborhoods.map(&:control_points).flatten.uniq(&:raw)
  end

  @dist_avg, @dist_std = *calculate_average_and_std(values: control_points.map(&:pdist), ignore_empty: true)
  @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)
  @count = control_points.count

  self
end

#distances_from_options(type: :horizontal) ⇒ Object



198
199
200
201
202
203
204
# File 'lib/panomosity/generalized_neighborhood.rb', line 198

def distances_from_options(type: :horizontal)
  if type == :both
    options.fetch(:distances, {})
  else
    options[:distances]&.fetch(type, {}) || {}
  end
end

#horizontal?Boolean

Returns:

  • (Boolean)


180
181
182
# File 'lib/panomosity/generalized_neighborhood.rb', line 180

def horizontal?
  pair_scope? ? scope.horizontal? : center.horizontal?
end

#idObject



148
149
150
# File 'lib/panomosity/generalized_neighborhood.rb', line 148

def id
  @id
end

#id=(id) ⇒ Object



152
153
154
# File 'lib/panomosity/generalized_neighborhood.rb', line 152

def id=(id)
  @id = id
end

#measure_distance?Boolean

Returns:

  • (Boolean)


176
177
178
# File 'lib/panomosity/generalized_neighborhood.rb', line 176

def measure_distance?
  measure.distance?
end

#measure_position?Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/panomosity/generalized_neighborhood.rb', line 172

def measure_position?
  measure.position?
end

#neighborhood_scope?Boolean

Returns:

  • (Boolean)


168
169
170
# File 'lib/panomosity/generalized_neighborhood.rb', line 168

def neighborhood_scope?
  scope.class.name == self.class.name
end

#pair_scope?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/panomosity/generalized_neighborhood.rb', line 164

def pair_scope?
  scope.class.name == 'Panomosity::Pair'
end

#referenceObject



156
157
158
# File 'lib/panomosity/generalized_neighborhood.rb', line 156

def reference
  @reference
end

#reference=(reference) ⇒ Object



160
161
162
# File 'lib/panomosity/generalized_neighborhood.rb', line 160

def reference=(reference)
  @reference = reference
end

#set_distance_defaultsObject



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/panomosity/generalized_neighborhood.rb', line 206

def set_distance_defaults
  return unless measure_position?

  measure.update_distances(distances_from_options(type: :both))

  if scope.horizontal?
    measure.update_distances(x2: (scope.first_image.h * 0.1).round)
    measure.update_distances(distances_from_options(type: :horizontal))
  else
    measure.update_distances(x1: (scope.first_image.w * 0.1).round)
    measure.update_distances(distances_from_options(type: :vertical))
  end

  measure.distances[:x1] ||= DEFAULT_DISTANCE
  measure.distances[:x2] ||= DEFAULT_DISTANCE
end

#set_measure_defaultsObject



223
224
225
226
227
228
229
230
231
232
233
# File 'lib/panomosity/generalized_neighborhood.rb', line 223

def set_measure_defaults
  center_values = if measure_position?
    { x1: center.x1, x2: center.y1 }
  elsif pair_scope?
    { x1: center.pdist }
  else
    { x1: center.dist_avg }
  end

  measure.update(center: center_values)
end

#typeObject



188
189
190
# File 'lib/panomosity/generalized_neighborhood.rb', line 188

def type
  horizontal? ? :horizontal : :vertical
end

#update_measure(params) ⇒ Object



192
193
194
195
196
# File 'lib/panomosity/generalized_neighborhood.rb', line 192

def update_measure(params)
  measure.update(params)
  set_distance_defaults
  set_measure_defaults
end

#vertical?Boolean

Returns:

  • (Boolean)


184
185
186
# File 'lib/panomosity/generalized_neighborhood.rb', line 184

def vertical?
  pair_scope? ? scope.vertical? : center.vertical?
end