Class: RTKIT::Plane
- Inherits:
-
Object
- Object
- RTKIT::Plane
- Defined in:
- lib/rtkit/plane.rb
Overview
A Plane describes a flat, two-dimensional surface. A Plane can be defined in several ways, e.g. by:
-
A point and a normal vector.
-
A point and two vectors lying on it.
-
3 non-colinear points.
We will describe the plane in the form of a plane equation:
ax + by +cz +d = 0
Notes
-
For more information on Planes, refer to: en.wikipedia.org/wiki/Plane_(geometry)
Relations
Since an image slice is located in a specific plane, the Plane class may be used to relate instances of such classes.
Instance Attribute Summary collapse
-
#a ⇒ Object
readonly
The a parameter of the plane equation.
-
#b ⇒ Object
readonly
The b parameter of the plane equation.
-
#c ⇒ Object
readonly
The c parameter of the plane equation.
-
#d ⇒ Object
readonly
The d parameter of the plane equation.
Class Method Summary collapse
-
.calculate(c1, c2, c3) ⇒ Object
Calculates a plane equation from the 3 specified coordinates.
-
.d ⇒ Object
The custom plane parameter d: This constant can be equal to any non-zero number.
Instance Method Summary collapse
-
#==(other) ⇒ Object
(also: #eql?)
Returns true if the argument is an instance with attributes equal to self.
-
#hash ⇒ Object
Generates a Fixnum hash value for this instance.
-
#initialize(a, b, c) ⇒ Plane
constructor
Creates a new Plane instance.
-
#match(planes) ⇒ Object
Compares the Plane instance with an array of planes, and returns the index of the Plane who’s Plane equation is closest to the plane equation of self.
-
#to_plane ⇒ Object
Returns self.
-
#to_s ⇒ Object
Converts the Plane instance to a readable string (containing the parameters a, b & c).
Constructor Details
#initialize(a, b, c) ⇒ Plane
Creates a new Plane instance.
Parameters
-
a
– Float. The a parameter of the plane equation. -
b
– Float. The b parameter of the plane equation. -
c
– Float. The c parameter of the plane equation.
77 78 79 80 81 82 83 84 85 |
# File 'lib/rtkit/plane.rb', line 77 def initialize(a, b, c) raise ArgumentError, "Invalid argument 'a'. Expected Float, got #{a.class}." unless a.is_a?(Float) raise ArgumentError, "Invalid argument 'b'. Expected Float, got #{b.class}." unless b.is_a?(Float) raise ArgumentError, "Invalid argument 'c'. Expected Float, got #{c.class}." unless c.is_a?(Float) @a = a @b = b @c = c @d = Plane.d end |
Instance Attribute Details
#a ⇒ Object (readonly)
The a parameter of the plane equation.
23 24 25 |
# File 'lib/rtkit/plane.rb', line 23 def a @a end |
#b ⇒ Object (readonly)
The b parameter of the plane equation.
25 26 27 |
# File 'lib/rtkit/plane.rb', line 25 def b @b end |
#c ⇒ Object (readonly)
The c parameter of the plane equation.
27 28 29 |
# File 'lib/rtkit/plane.rb', line 27 def c @c end |
#d ⇒ Object (readonly)
The d parameter of the plane equation.
29 30 31 |
# File 'lib/rtkit/plane.rb', line 29 def d @d end |
Class Method Details
.calculate(c1, c2, c3) ⇒ Object
Calculates a plane equation from the 3 specified coordinates. Returns a Plane instance.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/rtkit/plane.rb', line 34 def self.calculate(c1, c2, c3) raise ArgumentError, "Invalid argument 'c1'. Expected Coordinate, got #{c1.class}" unless c1.is_a?(Coordinate) raise ArgumentError, "Invalid argument 'c2'. Expected Coordinate, got #{c2.class}" unless c2.is_a?(Coordinate) raise ArgumentError, "Invalid argument 'c3'. Expected Coordinate, got #{c3.class}" unless c3.is_a?(Coordinate) x1, y1, z1 = c1.x.to_r, c1.y.to_r, c1.z.to_r x2, y2, z2 = c2.x.to_r, c2.y.to_r, c2.z.to_r x3, y3, z3 = c3.x.to_r, c3.y.to_r, c3.z.to_r raise ArgumentError, "Got at least two Coordinates that are equal. Expected unique Coordinates." unless [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]].uniq.length == 3 det = Matrix.rows([[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]).determinant if det == 0 # Haven't experienced this case yet. Just raise an error to avoid unexpected behaviour. raise "The determinant was zero (which means the plane passes through the origin). Not able to calculate variables: a,b,c" #puts "Determinant was zero. Plane passes through origin. Find direction cosines instead?" else det = det.to_f # Find parameters a,b,c. a_m = Matrix.rows([[1, y1, z1], [1, y2, z2], [1, y3, z3]]) b_m = Matrix.rows([[x1, 1, z1], [x2, 1, z2], [x3, 1, z3]]) c_m = Matrix.rows([[x1, y1, 1], [x2, y2, 1], [x3, y3, 1]]) d = Plane.d a = -d / det * a_m.determinant b = -d / det * b_m.determinant c = -d / det * c_m.determinant return self.new(a, b, c) end end |
.d ⇒ Object
The custom plane parameter d: This constant can be equal to any non-zero number. Returns the float value chosen in this implementation: 500.0
65 66 67 |
# File 'lib/rtkit/plane.rb', line 65 def self.d 500.0 end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
Returns true if the argument is an instance with attributes equal to self.
89 90 91 92 93 |
# File 'lib/rtkit/plane.rb', line 89 def ==(other) if other.respond_to?(:to_plane) other.send(:state) == state end end |
#hash ⇒ Object
Generates a Fixnum hash value for this instance.
99 100 101 |
# File 'lib/rtkit/plane.rb', line 99 def hash state.hash end |
#match(planes) ⇒ Object
Compares the Plane instance with an array of planes, and returns the index of the Plane who’s Plane equation is closest to the plane equation of self. Returns nil if no suitable match is found.
107 108 109 110 111 112 113 114 115 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 |
# File 'lib/rtkit/plane.rb', line 107 def match(planes) raise ArgumentError, "Invalid argument 'planes'. Expected Array, got #{planes.class}." unless planes.is_a?(Array) raise ArgumentError, "Invalid argument 'planes'. Expected Array containing only Planes, got #{planes.collect{|p| p.class}.uniq}." unless planes.collect{|p| p.class}.uniq == [Plane] # I don't really have a feeling for what a reasonable threshold should be here. Setting it at 0.01. # (So far, matched planes have been observed having a deviation in the order of 10e-5 to 10e-7, # while some obviously different planes have had values in the range of 8-21) deviation_threshold = 0.01 # Since the 'd' parameter is just a constant selected by us when determining plane equations, # the comparison is carried out against the a, b & c parameters. # Register deviation for each parameter for each plane: a_deviations = NArray.float(planes.length) b_deviations = NArray.float(planes.length) c_deviations = NArray.float(planes.length) planes.each_index do |i| # Calculate absolute deviation for each of the three parameters: a_deviations[i] = (planes[i].a - @a).abs b_deviations[i] = (planes[i].b - @b).abs c_deviations[i] = (planes[i].c - @c).abs end # Compare the deviations of each parameter with the average deviation for that parameter, # taking care to adress the case where all deviations for a certain parameter may be 0: a_relatives = a_deviations.mean == 0 ? a_deviations : a_deviations / a_deviations.mean b_relatives = b_deviations.mean == 0 ? b_deviations : b_deviations / b_deviations.mean c_relatives = c_deviations.mean == 0 ? c_deviations : c_deviations / c_deviations.mean # Sum the relative deviations for each parameter, and find the index with the lowest summed relative deviation: deviations = NArray.float(planes.length) planes.each_index do |i| deviations[i] = a_relatives[i] + b_relatives[i] + c_relatives[i] end index = (deviations.eq deviations.min).where[0] deviation = a_deviations[index] + b_deviations[index] + c_deviations[index] index = nil if deviation > deviation_threshold return index end |
#to_plane ⇒ Object
Returns self.
144 145 146 |
# File 'lib/rtkit/plane.rb', line 144 def to_plane self end |
#to_s ⇒ Object
Converts the Plane instance to a readable string (containing the parameters a, b & c).
150 151 152 |
# File 'lib/rtkit/plane.rb', line 150 def to_s return "a: #{@a.round(2)} b: #{@b.round(2)} c: #{@c.round(2)}" end |