1 /*=========================================================================
2 *
3 * Copyright Insight Software Consortium
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0.txt
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 *=========================================================================*/
18 #ifndef itkVersor_hxx
19 #define itkVersor_hxx
20
21 #include "itkVersor.h"
22 #include "itkNumericTraits.h"
23 #include "itkMath.h"
24 #include <vnl/vnl_det.h>
25
26 namespace itk
27 {
28 /** Constructor to initialize entire vector to one value. */
29 template< typename T >
30 Versor< T >
Versor()31 ::Versor() :
32 m_X(NumericTraits< T >::ZeroValue()),
33 m_Y(NumericTraits< T >::ZeroValue()),
34 m_Z(NumericTraits< T >::ZeroValue()),
35 m_W(NumericTraits< T >::OneValue())
36 {}
37
38 /** Copy Constructor */
39 template< typename T >
40 Versor< T >
Versor(const Self & v)41 ::Versor(const Self & v)
42 {
43 m_X = v.m_X;
44 m_Y = v.m_Y;
45 m_Z = v.m_Z;
46 m_W = v.m_W;
47 }
48
49 /** Assignment Operator */
50 template< typename T >
51 const Versor< T > &
52 Versor< T >
operator =(const Self & v)53 ::operator=(const Self & v)
54 {
55 m_X = v.m_X;
56 m_Y = v.m_Y;
57 m_Z = v.m_Z;
58 m_W = v.m_W;
59 return *this;
60 }
61
62 /** Set to an identity transform */
63 template< typename T >
64 void
65 Versor< T >
SetIdentity()66 ::SetIdentity()
67 {
68 m_X = NumericTraits< T >::ZeroValue();
69 m_Y = NumericTraits< T >::ZeroValue();
70 m_Z = NumericTraits< T >::ZeroValue();
71 m_W = NumericTraits< T >::OneValue();
72 }
73
74 /** Return a vnl_quaternion */
75 template< typename T >
76 vnl_quaternion< T >
77 Versor< T >
GetVnlQuaternion() const78 ::GetVnlQuaternion() const
79 {
80 return vnl_quaternion< T >(m_X, m_Y, m_Z, m_W);
81 }
82
83 /** Assignment and Composition Operator */
84 template< typename T >
85 const Versor< T > &
86 Versor< T >
operator *=(const Self & v)87 ::operator*=(const Self & v)
88 {
89 const double mx = m_W * v.m_X - m_Z * v.m_Y + m_Y * v.m_Z + m_X * v.m_W;
90 const double my = m_Z * v.m_X + m_W * v.m_Y - m_X * v.m_Z + m_Y * v.m_W;
91 const double mz = -m_Y * v.m_X + m_X * v.m_Y + m_W * v.m_Z + m_Z * v.m_W;
92 const double mw = -m_X * v.m_X - m_Y * v.m_Y - m_Z * v.m_Z + m_W * v.m_W;
93
94 m_X = mx;
95 m_Y = my;
96 m_Z = mz;
97 m_W = mw;
98
99 return *this;
100 }
101
102 /** Composition Operator */
103 template< typename T >
104 Versor< T >
105 Versor< T >
operator *(const Self & v) const106 ::operator*(const Self & v) const
107 {
108 Self result;
109
110 result.m_X = m_W * v.m_X - m_Z * v.m_Y + m_Y * v.m_Z + m_X * v.m_W;
111 result.m_Y = m_Z * v.m_X + m_W * v.m_Y - m_X * v.m_Z + m_Y * v.m_W;
112 result.m_Z = -m_Y * v.m_X + m_X * v.m_Y + m_W * v.m_Z + m_Z * v.m_W;
113 result.m_W = -m_X * v.m_X - m_Y * v.m_Y - m_Z * v.m_Z + m_W * v.m_W;
114
115 return result;
116 }
117
118 /** Division and Assignment Operator */
119 template< typename T >
120 const Versor< T > &
121 Versor< T >
operator /=(const Self & v)122 ::operator/=(const Self & v)
123 {
124 const double mx = -m_W * v.m_X + m_Z * v.m_Y - m_Y * v.m_Z + m_X * v.m_W;
125 const double my = -m_Z * v.m_X - m_W * v.m_Y + m_X * v.m_Z + m_Y * v.m_W;
126 const double mz = m_Y * v.m_X - m_X * v.m_Y - m_W * v.m_Z + m_Z * v.m_W;
127 const double mw = m_X * v.m_X + m_Y * v.m_Y + m_Z * v.m_Z + m_W * v.m_W;
128
129 m_X = mx;
130 m_Y = my;
131 m_Z = mz;
132 m_W = mw;
133
134 return *this;
135 }
136
137 /** Division Operator */
138 template< typename T >
139 Versor< T >
140 Versor< T >
operator /(const Self & v) const141 ::operator/(const Self & v) const
142 {
143 Self result;
144
145 result.m_X = -m_W * v.m_X + m_Z * v.m_Y - m_Y * v.m_Z + m_X * v.m_W;
146 result.m_Y = -m_Z * v.m_X - m_W * v.m_Y + m_X * v.m_Z + m_Y * v.m_W;
147 result.m_Z = m_Y * v.m_X - m_X * v.m_Y - m_W * v.m_Z + m_Z * v.m_W;
148 result.m_W = m_X * v.m_X + m_Y * v.m_Y + m_Z * v.m_Z + m_W * v.m_W;
149
150 return result;
151 }
152
153 /** Comparison operator */
154 template< typename T >
155 bool
156 Versor< T >
operator !=(const Self & v) const157 ::operator!=(const Self & v) const
158 {
159 return !( *this == v );
160 }
161
162 /** Comparison operator */
163 template< typename T >
164 bool
165 Versor< T >
operator ==(const Self & v) const166 ::operator==(const Self & v) const
167 {
168 // Evaluate the quaternion ratio between them
169 Self ratio = *this * v.GetReciprocal();
170
171 const typename itk::NumericTraits< T >::AccumulateType
172 square = ratio.m_W * ratio.m_W;
173
174 const double epsilon = 1e-300;
175
176 if ( std::fabs(1.0f - square) < epsilon )
177 {
178 return true;
179 }
180
181 return false;
182 }
183
184 /** Get Conjugate */
185 template< typename T >
186 Versor< T >
187 Versor< T >
GetConjugate() const188 ::GetConjugate() const
189 {
190 Self result;
191
192 result.m_X = -m_X;
193 result.m_Y = -m_Y;
194 result.m_Z = -m_Z;
195 result.m_W = m_W;
196
197 return result;
198 }
199
200 /** Get Reciprocal */
201 template< typename T >
202 Versor< T >
203 Versor< T >
GetReciprocal() const204 ::GetReciprocal() const
205 {
206 Self result;
207
208 result.m_X = -m_X;
209 result.m_Y = -m_Y;
210 result.m_Z = -m_Z;
211 result.m_W = m_W;
212
213 return result;
214 }
215
216 /** Get Tensor part */
217 template< typename T >
218 typename Versor< T >::ValueType
219 Versor< T >
GetTensor() const220 ::GetTensor() const
221 {
222 const auto tensor = static_cast< ValueType >(
223 std::sqrt(m_X * m_X + m_Y * m_Y + m_Z * m_Z + m_W * m_W) );
224
225 return tensor;
226 }
227
228 /** Normalize */
229 template< typename T >
230 void
231 Versor< T >
Normalize()232 ::Normalize()
233 {
234 const ValueType tensor = this->GetTensor();
235
236 if ( std::fabs(tensor) < 1e-20 )
237 {
238 ExceptionObject except;
239 except.SetDescription("Attempt to normalize a \
240 itk::Versor with zero tensor");
241 except.SetLocation(__FILE__);
242 throw except;
243 }
244 m_X /= tensor;
245 m_Y /= tensor;
246 m_Z /= tensor;
247 m_W /= tensor;
248 }
249
250 /** Get Axis */
251 template< typename T >
252 typename Versor< T >::VectorType
253 Versor< T >
GetAxis() const254 ::GetAxis() const
255 {
256 VectorType axis;
257
258 const auto ax = static_cast< RealType >( m_X );
259 const auto ay = static_cast< RealType >( m_Y );
260 const auto az = static_cast< RealType >( m_Z );
261
262 const RealType vectorNorm = std::sqrt(ax * ax + ay * ay + az * az);
263
264 if ( vectorNorm == NumericTraits< RealType >::ZeroValue() )
265 {
266 axis[0] = NumericTraits< T >::ZeroValue();
267 axis[1] = NumericTraits< T >::ZeroValue();
268 axis[2] = NumericTraits< T >::ZeroValue();
269 }
270 else
271 {
272 axis[0] = m_X / vectorNorm;
273 axis[1] = m_Y / vectorNorm;
274 axis[2] = m_Z / vectorNorm;
275 }
276
277 return axis;
278 }
279
280 /** Get Right part */
281 template< typename T >
282 typename Versor< T >::VectorType
283 Versor< T >
GetRight() const284 ::GetRight() const
285 {
286 VectorType axis;
287
288 axis[0] = m_X;
289 axis[1] = m_Y;
290 axis[2] = m_Z;
291
292 return axis;
293 }
294
295 /** Get Scalar part */
296 template< typename T >
297 typename Versor< T >::ValueType
298 Versor< T >
GetScalar() const299 ::GetScalar() const
300 {
301 return m_W;
302 }
303
304 /** Get Angle (in radians) */
305 template< typename T >
306 typename Versor< T >::ValueType
307 Versor< T >
GetAngle() const308 ::GetAngle() const
309 {
310 const auto ax = static_cast< RealType >( m_X );
311 const auto ay = static_cast< RealType >( m_Y );
312 const auto az = static_cast< RealType >( m_Z );
313
314 const RealType vectorNorm = std::sqrt(ax * ax + ay * ay + az * az);
315
316 const ValueType angle = 2.0 * std::atan2( vectorNorm, static_cast< RealType >( m_W ) );
317
318 return angle;
319 }
320
321 /** Get the Square root of the unit quaternion */
322 template< typename T >
323 Versor< T >
324 Versor< T >
SquareRoot() const325 ::SquareRoot() const
326 {
327 const ValueType newScalar = std::sqrt( static_cast< double >( 1.0 + m_W ) );
328 const double sqrtOfTwo = std::sqrt(2.0f);
329
330 const double factor = 1.0f / ( newScalar * sqrtOfTwo );
331
332 Self result;
333
334 result.m_X = m_X * factor;
335 result.m_Y = m_Y * factor;
336 result.m_Z = m_Z * factor;
337 result.m_W = newScalar / sqrtOfTwo;
338
339 return result;
340 }
341
342 /** Compute the Exponential of the quaternion */
343 template< typename T >
344 Versor< T >
345 Versor< T >
Exponential(ValueType exponent) const346 ::Exponential(ValueType exponent) const
347 {
348 Self result;
349
350 result.Set(this->GetAxis(),
351 this->GetAngle() * exponent);
352
353 return result;
354 }
355
356 /** Set Axis and Angle (in radians) */
357 template< typename T >
358 void
359 Versor< T >
Set(const VectorType & axis,ValueType angle)360 ::Set(const VectorType & axis, ValueType angle)
361 {
362 const RealType vectorNorm = axis.GetNorm();
363 if ( Math::FloatAlmostEqual<T>(vectorNorm, 0.0) )
364 {
365 ExceptionObject except;
366 except.SetDescription("Attempt to set rotation axis with zero norm");
367 except.SetLocation(__FILE__);
368 throw except;
369 }
370
371 const RealType cosangle2 = std::cos(angle / 2.0);
372 const RealType sinangle2 = std::sin(angle / 2.0);
373
374 const RealType factor = sinangle2 / vectorNorm;
375
376 m_X = axis[0] * factor;
377 m_Y = axis[1] * factor;
378 m_Z = axis[2] * factor;
379
380 m_W = cosangle2;
381 }
382
383 /** Set using an orthogonal matrix. */
384 template< typename T >
385 void
386 Versor< T >
Set(const MatrixType & mat)387 ::Set(const MatrixType & mat)
388 {
389 //const double epsilon = 1e-30;
390 //Keep the epsilon value large enough so that the alternate routes of
391 //computing the quaternion are used to within floating point precision of the
392 //math to be used. Using 1e-30 results in degenerate matries for rotations
393 //near itk::Math::pi due to imprecision of the math. 0.5/std::sqrt(trace) is
394 //not accurate to 1e-30, so the resulting matrices would have very large
395 //errors. By decreasing this epsilon value to a higher tolerance, the
396 //alternate stable methods for conversion are used.
397 //
398 //The use of std::numeric_limits< T >::epsilon() was not consistent with
399 //the rest of the ITK toolkit with respect to epsilon values for
400 //determining rotational orthogonality, and it occasionally
401 //prevented the conversion between different rigid transform types.
402
403 const T epsilon = Self::Epsilon(); // vnl_sqrt( std::numeric_limits< T >::epsilon() );
404 // Use a slightly less epsilon for detecting difference
405 const T epsilonDiff = Self::Epsilon(); //std::numeric_limits< T >::epsilon() * 10.0;
406
407 const vnl_matrix< T > m( mat.GetVnlMatrix() );
408
409 //check for orthonormality and that it isn't a reflection
410 const vnl_matrix_fixed< T, 3, 3 > & I = m*m.transpose();
411 if( std::abs( I[0][1] ) > epsilon || std::abs( I[0][2] ) > epsilon ||
412 std::abs( I[1][0] ) > epsilon || std::abs( I[1][2] ) > epsilon ||
413 std::abs( I[2][0] ) > epsilon || std::abs( I[2][1] ) > epsilon ||
414 std::abs( I[0][0] - itk::NumericTraits<T>::OneValue() ) > epsilonDiff ||
415 std::abs( I[1][1] - itk::NumericTraits<T>::OneValue() ) > epsilonDiff ||
416 std::abs( I[2][2] - itk::NumericTraits<T>::OneValue() ) > epsilonDiff ||
417 vnl_det( I ) < 0 )
418 {
419 itkGenericExceptionMacro(<< "The following matrix does not represent rotation to within an epsion of "
420 << epsilon << "." << std::endl
421 << m << std::endl
422 << "det(m * m transpose) is: " << vnl_det(I) << std::endl
423 << "m * m transpose is:" << std::endl
424 << I << std::endl);
425 }
426
427 const double trace = m(0, 0) + m(1, 1) + m(2, 2) + 1.0;
428
429 if ( trace > epsilon )
430 {
431 const double s = 0.5 / std::sqrt(trace);
432 m_W = 0.25 / s;
433 m_X = ( m(2, 1) - m(1, 2) ) * s;
434 m_Y = ( m(0, 2) - m(2, 0) ) * s;
435 m_Z = ( m(1, 0) - m(0, 1) ) * s;
436 }
437 else
438 {
439 if ( m(0, 0) > m(1, 1) && m(0, 0) > m(2, 2) )
440 {
441 const double s = 2.0 * std::sqrt( 1.0 + m(0, 0) - m(1, 1) - m(2, 2) );
442 m_X = 0.25 * s;
443 m_Y = ( m(0, 1) + m(1, 0) ) / s;
444 m_Z = ( m(0, 2) + m(2, 0) ) / s;
445 m_W = ( m(1, 2) - m(2, 1) ) / s;
446 }
447 else
448 {
449 if ( m(1, 1) > m(2, 2) )
450 {
451 const double s = 2.0 * std::sqrt( 1.0 + m(1, 1) - m(0, 0) - m(2, 2) );
452 m_X = ( m(0, 1) + m(1, 0) ) / s;
453 m_Y = 0.25 * s;
454 m_Z = ( m(1, 2) + m(2, 1) ) / s;
455 m_W = ( m(0, 2) - m(2, 0) ) / s;
456 }
457 else
458 {
459 const double s = 2.0 * std::sqrt( 1.0 + m(2, 2) - m(0, 0) - m(1, 1) );
460 m_X = ( m(0, 2) + m(2, 0) ) / s;
461 m_Y = ( m(1, 2) + m(2, 1) ) / s;
462 m_Z = 0.25 * s;
463 m_W = ( m(0, 1) - m(1, 0) ) / s;
464 }
465 }
466 }
467 this->Normalize();
468 }
469
470 /** Set right Part (in radians) */
471 template< typename T >
472 void
473 Versor< T >
Set(const VectorType & axis)474 ::Set(const VectorType & axis)
475 {
476 const ValueType sinangle2 = axis.GetNorm();
477 if ( sinangle2 > NumericTraits< ValueType >::OneValue() )
478 {
479 ExceptionObject exception;
480 exception.SetDescription("Trying to initialize a Versor with " \
481 "a vector whose magnitude is greater than 1");
482 exception.SetLocation("itk::Versor::Set( const VectorType )");
483 throw exception;
484 }
485
486 const ValueType cosangle2 = std::sqrt(NumericTraits< double >::OneValue() - sinangle2 * sinangle2);
487
488 m_X = axis[0];
489 m_Y = axis[1];
490 m_Z = axis[2];
491
492 m_W = cosangle2;
493 }
494
495 /** Set the Versor from four components.
496 * After assignment, the quaternion is normalized
497 * in order to get a consistent Versor (unit quaternion). */
498 template< typename T >
499 void
500 Versor< T >
Set(T x,T y,T z,T w)501 ::Set(T x, T y, T z, T w)
502 {
503 //
504 // We assume in this class that the W component is always non-negative.
505 // The rotation represented by a Versor remains unchanged if all its
506 // four components are negated simultaneously. Therefore, if we are
507 // requested to initialize a Versor with a negative W, we negate the
508 // signs of all the components.
509 //
510 if ( w < 0.0 )
511 {
512 m_X = -x;
513 m_Y = -y;
514 m_Z = -z;
515 m_W = -w;
516 }
517 else
518 {
519 m_X = x;
520 m_Y = y;
521 m_Z = z;
522 m_W = w;
523 }
524 this->Normalize();
525 }
526
527 /** Set from a vnl_quaternion
528 * After assignment, the quaternion is normalized
529 * in order to get a consistent Versor (unit quaternion). */
530 template< typename T >
531 void
532 Versor< T >
Set(const VnlQuaternionType & quaternion)533 ::Set(const VnlQuaternionType & quaternion)
534 {
535 m_X = quaternion.x();
536 m_Y = quaternion.y();
537 m_Z = quaternion.z();
538 m_W = quaternion.r();
539 this->Normalize();
540 }
541
542 /** Set rotation around X axis */
543 template< typename T >
544 void
545 Versor< T >
SetRotationAroundX(ValueType angle)546 ::SetRotationAroundX(ValueType angle)
547 {
548 const ValueType sinangle2 = std::sin(angle / 2.0);
549 const ValueType cosangle2 = std::cos(angle / 2.0);
550
551 m_X = sinangle2;
552 m_Y = NumericTraits< T >::ZeroValue();
553 m_Z = NumericTraits< T >::ZeroValue();
554 m_W = cosangle2;
555 }
556
557 /** Set rotation around Y axis */
558 template< typename T >
559 void
560 Versor< T >
SetRotationAroundY(ValueType angle)561 ::SetRotationAroundY(ValueType angle)
562 {
563 const ValueType sinangle2 = std::sin(angle / 2.0);
564 const ValueType cosangle2 = std::cos(angle / 2.0);
565
566 m_X = NumericTraits< T >::ZeroValue();
567 m_Y = sinangle2;
568 m_Z = NumericTraits< T >::ZeroValue();
569 m_W = cosangle2;
570 }
571
572 /** Set rotation around Z axis */
573 template< typename T >
574 void
575 Versor< T >
SetRotationAroundZ(ValueType angle)576 ::SetRotationAroundZ(ValueType angle)
577 {
578 const ValueType sinangle2 = std::sin(angle / 2.0);
579 const ValueType cosangle2 = std::cos(angle / 2.0);
580
581 m_X = NumericTraits< T >::ZeroValue();
582 m_Y = NumericTraits< T >::ZeroValue();
583 m_Z = sinangle2;
584 m_W = cosangle2;
585 }
586
587 namespace {
588 template< typename InputVectorType, typename ValueType, typename OutputVectorType >
localTransformVectorMath(const InputVectorType & VectorObject,const ValueType & inputX,const ValueType & inputY,const ValueType & inputZ,const ValueType & inputW)589 inline const OutputVectorType localTransformVectorMath(const InputVectorType & VectorObject,
590 const ValueType & inputX,
591 const ValueType & inputY,
592 const ValueType & inputZ,
593 const ValueType & inputW)
594 {
595 const ValueType xx = inputX * inputX;
596 const ValueType yy = inputY * inputY;
597 const ValueType zz = inputZ * inputZ;
598 const ValueType xy = inputX * inputY;
599 const ValueType xz = inputX * inputZ;
600 const ValueType xw = inputX * inputW;
601 const ValueType yz = inputY * inputZ;
602 const ValueType yw = inputY * inputW;
603 const ValueType zw = inputZ * inputW;
604
605 const ValueType mxx = 1.0 - 2.0 * ( yy + zz );
606 const ValueType myy = 1.0 - 2.0 * ( xx + zz );
607 const ValueType mzz = 1.0 - 2.0 * ( xx + yy );
608 const ValueType mxy = 2.0 * ( xy - zw );
609 const ValueType mxz = 2.0 * ( xz + yw );
610 const ValueType myx = 2.0 * ( xy + zw );
611 const ValueType mzx = 2.0 * ( xz - yw );
612 const ValueType mzy = 2.0 * ( yz + xw );
613 const ValueType myz = 2.0 * ( yz - xw );
614
615 OutputVectorType result;
616 result[0] = mxx * VectorObject[0] + mxy * VectorObject[1] + mxz * VectorObject[2];
617 result[1] = myx * VectorObject[0] + myy * VectorObject[1] + myz * VectorObject[2];
618 result[2] = mzx * VectorObject[0] + mzy * VectorObject[1] + mzz * VectorObject[2];
619 return result;
620 }
621 }
622
623 /** Transform a Vector */
624 template< typename T >
625 typename Versor< T >::VectorType
626 Versor< T >
Transform(const VectorType & v) const627 ::Transform(const VectorType & v) const
628 {
629 return localTransformVectorMath<VectorType,T,typename Versor< T >::VectorType>(v,this->m_X,this->m_Y,this->m_Z,this->m_W);
630 }
631
632 /** Transform a CovariantVector
633 * given that this is an orthogonal transformation
634 * CovariantVectors are transformed as vectors. */
635 template< typename T >
636 typename Versor< T >::CovariantVectorType
637 Versor< T >
Transform(const CovariantVectorType & v) const638 ::Transform(const CovariantVectorType & v) const
639 {
640 return localTransformVectorMath<CovariantVectorType,T,typename Versor< T >::CovariantVectorType>(v,this->m_X,this->m_Y,this->m_Z,this->m_W);
641 }
642
643 /** Transform a Point */
644 template< typename T >
645 typename Versor< T >::PointType
646 Versor< T >
Transform(const PointType & v) const647 ::Transform(const PointType & v) const
648 {
649 return localTransformVectorMath<PointType,T,typename Versor< T >::PointType>(v,this->m_X,this->m_Y,this->m_Z,this->m_W);
650 }
651
652 /** Transform a VnlVector */
653 template< typename T >
654 typename Versor< T >::VnlVectorType
655 Versor< T >
Transform(const VnlVectorType & v) const656 ::Transform(const VnlVectorType & v) const
657 {
658 return localTransformVectorMath<VnlVectorType,T,typename Versor< T >::VnlVectorType>(v,this->m_X,this->m_Y,this->m_Z,this->m_W);
659 }
660
661 /** Get Matrix representation */
662 template< typename T >
663 Matrix< T, 3, 3 >
664 Versor< T >
GetMatrix() const665 ::GetMatrix() const
666 {
667 Matrix< T, 3, 3 > matrix;
668
669 const RealType xx = m_X * m_X;
670 const RealType yy = m_Y * m_Y;
671 const RealType zz = m_Z * m_Z;
672 const RealType xy = m_X * m_Y;
673 const RealType xz = m_X * m_Z;
674 const RealType xw = m_X * m_W;
675 const RealType yz = m_Y * m_Z;
676 const RealType yw = m_Y * m_W;
677 const RealType zw = m_Z * m_W;
678
679 matrix[0][0] = 1.0 - 2.0 * ( yy + zz );
680 matrix[1][1] = 1.0 - 2.0 * ( xx + zz );
681 matrix[2][2] = 1.0 - 2.0 * ( xx + yy );
682 matrix[0][1] = 2.0 * ( xy - zw );
683 matrix[0][2] = 2.0 * ( xz + yw );
684 matrix[1][0] = 2.0 * ( xy + zw );
685 matrix[2][0] = 2.0 * ( xz - yw );
686 matrix[2][1] = 2.0 * ( yz + xw );
687 matrix[1][2] = 2.0 * ( yz - xw );
688
689 return matrix;
690 }
691 } // end namespace itk
692
693 #endif
694