TuttleOFX
1
|
00001 #include "PinningPlugin.hpp" 00002 #include "PinningProcess.hpp" 00003 #include "PinningDefinitions.hpp" 00004 00005 #include <tuttle/plugin/global.hpp> 00006 00007 #include <ofxsImageEffect.h> 00008 #include <ofxsMultiThread.h> 00009 00010 #include <boost/gil/gil_all.hpp> 00011 #include <boost/numeric/conversion/cast.hpp> 00012 #include <boost/numeric/ublas/io.hpp> 00013 #include <boost/numeric/ublas/lu.hpp> 00014 #include <boost/numeric/ublas/matrix.hpp> 00015 #include <boost/numeric/ublas/vector.hpp> 00016 #include <boost/algorithm/string/predicate.hpp> 00017 00018 namespace tuttle { 00019 namespace plugin { 00020 namespace pinning { 00021 00022 PinningPlugin::PinningPlugin( OfxImageEffectHandle handle ) 00023 : SamplerPlugin( handle ) 00024 { 00025 _clipSrc = fetchClip( kOfxImageEffectSimpleSourceClipName ); 00026 _clipDst = fetchClip( kOfxImageEffectOutputClipName ); 00027 00028 _paramMethod = fetchChoiceParam( kParamMethod ); 00029 // _paramManipulatorMode = fetchChoiceParam( kParamManipulatorMode ); 00030 _paramSetToCornersIn = fetchPushButtonParam( kParamSetToCornersIn ); 00031 _paramSetToCornersOut = fetchPushButtonParam( kParamSetToCornersOut ); 00032 _paramOverlay = fetchBooleanParam( kParamOverlay ); 00033 _paramInverse = fetchBooleanParam( kParamInverse ); 00034 00035 /* 00036 //TODO-vince // 00037 _paramGroupCentre = fetchGroupParam( kParamGroupCentre ); 00038 _paramPointCentre = fetchDouble2DParam( kParamPointCentre); 00039 _paramOverlayCentre = fetchBooleanParam( kParamOverlayCentre ); 00040 _paramOverlayCentreColor = fetchRGBParam( kParamOverlayCentreColor ); 00041 /////////////////////// 00042 */ 00043 00044 _paramGroupIn = fetchGroupParam( kParamGroupIn ); 00045 _paramPointIn0 = fetchDouble2DParam( kParamPointIn + "0" ); 00046 _paramPointIn1 = fetchDouble2DParam( kParamPointIn + "1" ); 00047 _paramPointIn2 = fetchDouble2DParam( kParamPointIn + "2" ); 00048 _paramPointIn3 = fetchDouble2DParam( kParamPointIn + "3" ); 00049 _paramOverlayIn = fetchBooleanParam( kParamOverlayIn ); 00050 _paramOverlayInColor = fetchRGBParam( kParamOverlayInColor ); 00051 00052 _paramGroupOut = fetchGroupParam( kParamGroupIn ); 00053 _paramPointOut0 = fetchDouble2DParam( kParamPointOut + "0" ); 00054 _paramPointOut1 = fetchDouble2DParam( kParamPointOut + "1" ); 00055 _paramPointOut2 = fetchDouble2DParam( kParamPointOut + "2" ); 00056 _paramPointOut3 = fetchDouble2DParam( kParamPointOut + "3" ); 00057 _paramOverlayOut = fetchBooleanParam( kParamOverlayOut ); 00058 _paramOverlayOutColor = fetchRGBParam( kParamOverlayOutColor ); 00059 00060 _paramGroupPerspMatrix = fetchGroupParam( kParamGroupPerspMatrix ); 00061 _paramPerspMatrixRow0 = fetchDouble3DParam( kParamPerspMatrixRow + "0" ); 00062 _paramPerspMatrixRow1 = fetchDouble3DParam( kParamPerspMatrixRow + "1" ); 00063 _paramPerspMatrixRow2 = fetchDouble3DParam( kParamPerspMatrixRow + "2" ); 00064 00065 _paramGroupBilMatrix = fetchGroupParam( kParamGroupBilinearMatrix ); 00066 _paramBilMatrixRow0 = fetchDouble2DParam( kParamBilinearMatrixRow + "0" ); 00067 _paramBilMatrixRow1 = fetchDouble2DParam( kParamBilinearMatrixRow + "1" ); 00068 _paramBilMatrixRow2 = fetchDouble2DParam( kParamBilinearMatrixRow + "2" ); 00069 _paramBilMatrixRow3 = fetchDouble2DParam( kParamBilinearMatrixRow + "3" ); 00070 00071 changedParam( OFX::InstanceChangedArgs(), kParamMethod ); 00072 changedParam( OFX::InstanceChangedArgs(), kParamFilter ); 00073 } 00074 00075 PinningProcessParams<PinningPlugin::Scalar> PinningPlugin::getProcessParams( const OfxTime time, const OfxPointD& renderScale ) const 00076 { 00077 using namespace boost::numeric::ublas; 00078 PinningProcessParams<Scalar> params; 00079 00080 const OfxRectD rod = _clipSrc->getCanonicalRod( time, renderScale ); 00081 const double width = rod.x2 - rod.x1; 00082 const double height = rod.y2 - rod.y1; 00083 00084 // persp matrix 00085 bounded_matrix<double, 3, 3 > pm; 00086 _paramPerspMatrixRow0->getValue( pm( 0, 0 ), pm( 0, 1 ), pm( 0, 2 ) ); 00087 _paramPerspMatrixRow1->getValue( pm( 1, 0 ), pm( 1, 1 ), pm( 1, 2 ) ); 00088 _paramPerspMatrixRow2->getValue( pm( 2, 0 ), pm( 2, 1 ), pm( 2, 2 ) ); 00089 params._perspective._matrix = pm; 00090 params._perspective._width = width; 00091 params._perspective._height = height; 00092 00093 // bilinear matrix 00094 bounded_matrix<double, 2, 4 > bm; 00095 _paramBilMatrixRow0->getValue( bm( 0, 0 ), bm( 1, 0 ) ); 00096 _paramBilMatrixRow1->getValue( bm( 0, 1 ), bm( 1, 1 ) ); 00097 _paramBilMatrixRow2->getValue( bm( 0, 2 ), bm( 1, 2 ) ); 00098 _paramBilMatrixRow3->getValue( bm( 0, 3 ), bm( 1, 3 ) ); 00099 params._bilinear._matrix = bm; 00100 params._bilinear._width = width; 00101 params._bilinear._height = height; 00102 00103 params._method = static_cast<EParamMethod>( _paramMethod->getValue() ); 00104 00105 SamplerPlugin::fillProcessParams( params._samplerProcessParams ); 00106 00107 return params; 00108 } 00109 00110 void PinningPlugin::changedParam( const OFX::InstanceChangedArgs& args, const std::string& paramName ) 00111 { 00112 using namespace boost::numeric::ublas; 00113 00114 bounded_vector<double, 2> pOut[4]; 00115 bounded_vector<double, 2> pIn[4]; 00116 00117 SamplerPlugin::changedParam( args, paramName ); 00118 00119 if( _paramInverse->getValue() ) 00120 { 00121 _paramPointIn0->getValue( pOut[0][0], pOut[0][1] ); 00122 _paramPointIn1->getValue( pOut[1][0], pOut[1][1] ); 00123 _paramPointIn2->getValue( pOut[2][0], pOut[2][1] ); 00124 _paramPointIn3->getValue( pOut[3][0], pOut[3][1] ); 00125 00126 _paramPointOut0->getValue( pIn[0][0], pIn[0][1] ); 00127 _paramPointOut1->getValue( pIn[1][0], pIn[1][1] ); 00128 _paramPointOut2->getValue( pIn[2][0], pIn[2][1] ); 00129 _paramPointOut3->getValue( pIn[3][0], pIn[3][1] ); 00130 } 00131 else 00132 { 00133 _paramPointOut0->getValue( pOut[0][0], pOut[0][1] ); 00134 _paramPointOut1->getValue( pOut[1][0], pOut[1][1] ); 00135 _paramPointOut2->getValue( pOut[2][0], pOut[2][1] ); 00136 _paramPointOut3->getValue( pOut[3][0], pOut[3][1] ); 00137 00138 _paramPointIn0->getValue( pIn[0][0], pIn[0][1] ); 00139 _paramPointIn1->getValue( pIn[1][0], pIn[1][1] ); 00140 _paramPointIn2->getValue( pIn[2][0], pIn[2][1] ); 00141 _paramPointIn3->getValue( pIn[3][0], pIn[3][1] ); 00142 } 00143 00144 if(paramName == kParamSetToCornersIn) 00145 { 00146 _paramPointIn0->setValue( -0.5, -0.5 ); 00147 _paramPointIn1->setValue( 0.5, -0.5 ); 00148 _paramPointIn2->setValue( -0.5, 0.5 ); 00149 _paramPointIn3->setValue( 0.5, 0.5 ); 00150 } 00151 else if(paramName == kParamSetToCornersOut) 00152 { 00153 _paramPointOut0->setValue( -0.5, -0.5 ); 00154 _paramPointOut1->setValue( 0.5, -0.5 ); 00155 _paramPointOut2->setValue( -0.5, 0.5 ); 00156 _paramPointOut3->setValue( 0.5, 0.5); 00157 } 00158 else if( paramName == kParamMethod ) 00159 { 00160 bool bil = false; 00161 bool persp = false; 00162 bool fourPoints = false; 00163 00164 switch( static_cast<EParamMethod>( _paramMethod->getValue() ) ) 00165 { 00166 case eParamMethodAffine: 00167 { 00168 persp = true; 00169 break; 00170 } 00171 case eParamMethodPerspective: 00172 { 00173 fourPoints = true; 00174 persp = true; 00175 break; 00176 } 00177 case eParamMethodBilinear: 00178 { 00179 fourPoints = true; 00180 bil = true; 00181 break; 00182 } 00183 } 00184 00185 _paramPointIn3->setIsSecretAndDisabled( !fourPoints ); 00186 _paramPointOut3->setIsSecretAndDisabled( !fourPoints ); 00187 00188 _paramGroupPerspMatrix->setIsSecretAndDisabled( !persp ); 00189 _paramPerspMatrixRow0->setIsSecretAndDisabled( !persp ); 00190 _paramPerspMatrixRow1->setIsSecretAndDisabled( !persp ); 00191 _paramPerspMatrixRow2->setIsSecretAndDisabled( !persp ); 00192 00193 _paramGroupBilMatrix->setIsSecretAndDisabled( !bil ); 00194 _paramBilMatrixRow0->setIsSecretAndDisabled( !bil ); 00195 _paramBilMatrixRow1->setIsSecretAndDisabled( !bil ); 00196 _paramBilMatrixRow2->setIsSecretAndDisabled( !bil ); 00197 _paramBilMatrixRow3->setIsSecretAndDisabled( !bil ); 00198 00199 // recompute the matrix 00200 changedParam( args, kParamPointIn ); 00201 } 00202 else if( 00203 boost::starts_with( paramName, kParamPointIn ) || 00204 boost::starts_with( paramName, kParamPointOut ) || 00205 paramName == kParamInverse 00206 ) 00207 { 00208 switch( static_cast < EParamMethod >( _paramMethod->getValue() ) ) 00209 { 00210 case eParamMethodAffine: 00211 { 00212 bounded_matrix<Scalar, 3, 3> perspMatrix; 00213 00214 //////////////////////////////////////// 00215 // compute "perspMatrix" from input/output points -> "_paramPoint*" 00216 perspMatrix = identity_matrix<Scalar >( 3 ); 00217 00218 /* Calculates coefficients of affine transformation 00219 * which maps (xi,yi) to (ui,vi), (i=1,2,3): 00220 * 00221 * ui = c00*xi + c01*yi + c02 00222 * 00223 * vi = c10*xi + c11*yi + c12 00224 * 00225 * Coefficients are calculated by solving linear system: 00226 * / x0 y0 1 0 0 0 \ /c00\ /u0\ 00227 * | x1 y1 1 0 0 0 | |c01| |u1| 00228 * | x2 y2 1 0 0 0 | |c02| |u2| 00229 * | 0 0 0 x0 y0 1 | |c10| |v0| 00230 * | 0 0 0 x1 y1 1 | |c11| |v1| 00231 * \ 0 0 0 x2 y2 1 / |c12| |v2| 00232 * 00233 * where: 00234 * cij - matrix coefficients 00235 */ 00236 static const int n = 6; 00237 permutation_matrix<double> P( n ); 00238 matrix<double> A( n, n ); 00239 vector<double> x( n ); 00240 vector<double> b( n ); 00241 00242 ///////////////////// 00243 // fill A and b... // 00244 for( int i = 0; i < 3; ++i ) 00245 { 00246 A( i, 0 ) = A( i + 3, 0 + 3 ) = pOut[i][0]; 00247 A( i, 1 ) = A( i + 3, 1 + 3 ) = pOut[i][1]; 00248 b( i ) = pIn[i][0]; 00249 b( i + 3 ) = pIn[i][1]; 00250 } 00251 subrange( A, 3,6, 0,3 ) = subrange( A, 0,3, 3,6 ) = zero_matrix<double>(n); 00252 subrange( A, 0,3, 2,3 ) = subrange( A, 3,6, 5,6 ) = scalar_matrix<double>(3,1, 1); 00253 00254 // TUTTLE_LOG_VAR( TUTTLE_INFO, A ); 00255 00256 lu_factorize( A, P ); 00257 // Now A and P contain the LU factorization of A 00258 x = b; 00259 lu_substitute( A, P, x ); 00260 // Now x contains the solution. 00261 00262 _paramPerspMatrixRow0->setValue( x( 0 ), x( 1 ), x( 2 ) ); 00263 _paramPerspMatrixRow1->setValue( x( 3 ), x( 4 ), x( 5 ) ); 00264 _paramPerspMatrixRow2->setValue( 0, 0, 1 ); 00265 break; 00266 } 00267 case eParamMethodPerspective: 00268 { 00269 bounded_matrix<double, 3, 3 > perspMatrix; 00270 00271 //////////////////////////////////////// 00272 // compute "perspMatrix" from input/output points -> "_paramPoint*" 00273 perspMatrix = identity_matrix<Scalar >( 3 ); 00274 //////////////////////////////////////// 00275 /* Calculates coefficients of perspective transformation 00276 * which maps (xi,yi) to (ui,vi), (i=1,2,3,4): 00277 * 00278 * c00*xi + c01*yi + c02 00279 * ui = --------------------- 00280 * c20*xi + c21*yi + c22 00281 * 00282 * c10*xi + c11*yi + c12 00283 * vi = --------------------- 00284 * c20*xi + c21*yi + c22 00285 * 00286 * Coefficients are calculated by solving linear system: 00287 * / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\ 00288 * | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1| 00289 * | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2| 00290 * | x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|, 00291 * | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0| 00292 * | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1| 00293 * | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2| 00294 * \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/ 00295 * 00296 * where: 00297 * cij - matrix coefficients, c22 = 1 00298 */ 00299 00300 static const int n = 8; 00301 permutation_matrix<double> P( n ); 00302 matrix<double> A( n, n ); 00303 vector<double> x( n ); 00304 vector<double> b( n ); 00305 00306 ///////////////////// 00307 // fill A and b... // 00308 for( int i = 0; i < 4; ++i ) 00309 { 00310 A(i,0) = A(i+4,3) = pOut[i][0]; 00311 A(i,1) = A(i+4,4) = pOut[i][1]; 00312 A(i,2) = A(i+4,5) = 1.0; 00313 A(i,3) = A(i,4) = A(i,5) = A(i+4,0) = A(i+4,1) = A(i+4,2) = 0.0; 00314 00315 A(i,6) = -(pOut[i][0])*(pIn[i][0]); 00316 A(i,7) = -(pOut[i][1])*(pIn[i][0]); 00317 A(i+4,6) = -(pOut[i][0])*(pIn[i][1]); 00318 A(i+4,7) = -(pOut[i][1])*(pIn[i][1]); 00319 00320 b(i) = pIn[i][0]; 00321 b(i+4) = pIn[i][1]; 00322 } 00323 //TUTTLE_LOG_VAR( TUTTLE_INFO, A ); 00324 00325 lu_factorize( A, P ); 00326 // Now A and P contain the LU factorization of A 00327 x = b; 00328 lu_substitute( A, P, x ); 00329 // Now x contains the solution. 00330 00331 _paramPerspMatrixRow0->setValue( x( 0 ), x( 1 ), x( 2 ) ); 00332 _paramPerspMatrixRow1->setValue( x( 3 ), x( 4 ), x( 5 ) ); 00333 _paramPerspMatrixRow2->setValue( x( 6 ), x( 7 ), 1 ); 00334 00335 break; 00336 } 00337 case eParamMethodBilinear: 00338 { 00339 bounded_matrix<Scalar, 2, 4 > bilMatrix; 00340 //////////////////////////////////////// 00341 ///compute "bilMatrix" from input/output points -> "_paramPoint*" 00342 bilMatrix( 0, 0 ) = 1.0; 00343 bilMatrix( 0, 1 ) = 0.0; 00344 bilMatrix( 0, 2 ) = 0.0; 00345 bilMatrix( 0, 3 ) = 0.0; 00346 bilMatrix( 1, 0 ) = 0.0; 00347 bilMatrix( 1, 1 ) = 1.0; 00348 bilMatrix( 1, 2 ) = 0.0; 00349 bilMatrix( 1, 3 ) = 2.0; 00350 //////////////////////////////////////// 00351 /* Coefficients are calculated by solving linear system: 00352 * / x0 y0 x0y0 1 0 0 0 0 \ 00353 * | 0 0 0 0 0 x0 y0 x0y0 1 | 00354 * | x1 y1 x1y1 1 0 0 0 0 | 00355 * | 0 0 0 0 0 x1 y1 x0y1 1 | 00356 * | x2 y2 x2y2 1 0 0 0 0 | 00357 * | 0 0 0 0 0 x2 y2 x2y2 1 | 00358 * | x3 y3 x3y3 1 0 0 0 0 | 00359 * \ 0 0 0 0 0 x3 y3 x3y3 1 / 00360 */ 00361 // Recuperation des points IN et OUT 00362 00363 static const int n = 8; 00364 permutation_matrix<double> P( n ); 00365 matrix<double> A(n, n); 00366 vector<double> c( n ); 00367 vector<double> b( n ); 00368 00369 for( int i = 0; i < 4; ++i ) 00370 { 00371 A( 2*i, 0 ) = A( 2*i + 1, 0 + 4 ) = pOut[i][0]; 00372 A( 2*i, 1 ) = A( 2*i + 1, 1 + 4 ) = pOut[i][1]; 00373 A( 2*i, 2 ) = A( 2*i + 1, 2 + 4 ) = pOut[i][0]*pOut[i][1]; 00374 A( 2*i, 3 ) = A( 2*i + 1, 3 + 4 ) = 1; 00375 00376 A(2*i, 4) = A(2*i, 5) = A(2*i, 6) = A(2*i, 7) = 0; 00377 A(2*i+1, 0) = A(2*i+1, 1) = A(2*i+1, 2) = A(2*i+1, 3) = 0; 00378 00379 b( i*2 ) = pIn[i][0]; 00380 b( i*2 + 1 ) = pIn[i][1]; 00381 } 00382 00383 lu_factorize( A, P ); 00384 // Now A and P contain the LU factorization of A 00385 c = b; 00386 00387 lu_substitute( A, P, c ); 00388 // Now bilMatrix contains the solution. 00389 00390 _paramBilMatrixRow0->setValue( c( 0 ), c( 4 ) ); 00391 _paramBilMatrixRow1->setValue( c( 1 ), c( 5 ) ); 00392 _paramBilMatrixRow2->setValue( c( 2 ), c( 6 ) ); 00393 _paramBilMatrixRow3->setValue( c( 3 ), c( 7 ) ); 00394 00395 break; 00396 } 00397 } 00398 } 00399 } 00400 00401 bool PinningPlugin::isIdentity( const OFX::RenderArguments& args, OFX::Clip*& identityClip, double& identityTime ) 00402 { 00403 return false; /// @todo remove this. 00404 using namespace boost::numeric::ublas; 00405 PinningProcessParams<Scalar> params = getProcessParams( args.time, args.renderScale ); 00406 bool identity = false; 00407 00408 // is the transformation matrix is an identity matrix the node is identity, 00409 // we perform no modification on the input image. 00410 switch( params._method ) 00411 { 00412 case eParamMethodAffine: 00413 case eParamMethodPerspective: 00414 { 00415 if( norm_inf( params._perspective._matrix - identity_matrix<Scalar >( 3 ) ) == 0 ) 00416 { 00417 identity = true; 00418 } 00419 break; 00420 } 00421 case eParamMethodBilinear: 00422 { 00423 if( params._bilinear._matrix( 0, 0 ) == 1.0 && 00424 params._bilinear._matrix( 0, 1 ) == 0.0 && 00425 params._bilinear._matrix( 0, 2 ) == 0.0 && 00426 params._bilinear._matrix( 0, 3 ) == 0.0 && 00427 params._bilinear._matrix( 1, 0 ) == 0.0 && 00428 params._bilinear._matrix( 1, 1 ) == 1.0 && 00429 params._bilinear._matrix( 1, 2 ) == 0.0 && 00430 params._bilinear._matrix( 1, 3 ) == 0.0 ) 00431 { 00432 identity = true; 00433 } 00434 break; 00435 } 00436 } 00437 identityClip = _clipSrc; 00438 identityTime = args.time; 00439 return identity; 00440 } 00441 00442 /** 00443 * @brief The overridden render function 00444 * @param[in] args Rendering parameters 00445 */ 00446 void PinningPlugin::render( const OFX::RenderArguments& args ) 00447 { 00448 using namespace boost::gil; 00449 // instantiate the render code based on the pixel depth of the dst clip 00450 OFX::EBitDepth dstBitDepth = _clipDst->getPixelDepth(); 00451 OFX::EPixelComponent dstComponents = _clipDst->getPixelComponents(); 00452 00453 // do the rendering 00454 if( dstComponents == OFX::ePixelComponentRGBA ) 00455 { 00456 switch( dstBitDepth ) 00457 { 00458 case OFX::eBitDepthUByte: 00459 { 00460 PinningProcess<rgba8_view_t> p( *this ); 00461 p.setupAndProcess( args ); 00462 break; 00463 } 00464 case OFX::eBitDepthUShort: 00465 { 00466 PinningProcess<rgba16_view_t> p( *this ); 00467 p.setupAndProcess( args ); 00468 break; 00469 } 00470 case OFX::eBitDepthFloat: 00471 { 00472 PinningProcess<rgba32f_view_t> p( *this ); 00473 p.setupAndProcess( args ); 00474 break; 00475 } 00476 case OFX::eBitDepthNone: 00477 case OFX::eBitDepthCustom: 00478 { 00479 TUTTLE_LOG_ERROR( "Bit depth (" << mapBitDepthEnumToString(dstBitDepth) << ") not recognized by the plugin." ); 00480 break; 00481 } 00482 } 00483 } 00484 else if( dstComponents == OFX::ePixelComponentAlpha ) 00485 { 00486 switch( dstBitDepth ) 00487 { 00488 case OFX::eBitDepthUByte: 00489 { 00490 PinningProcess<gray8_view_t> p( *this ); 00491 p.setupAndProcess( args ); 00492 break; 00493 } 00494 case OFX::eBitDepthUShort: 00495 { 00496 PinningProcess<gray16_view_t> p( *this ); 00497 p.setupAndProcess( args ); 00498 break; 00499 } 00500 case OFX::eBitDepthFloat: 00501 { 00502 PinningProcess<gray32f_view_t> p( *this ); 00503 p.setupAndProcess( args ); 00504 break; 00505 } 00506 case OFX::eBitDepthNone: 00507 case OFX::eBitDepthCustom: 00508 { 00509 TUTTLE_LOG_ERROR( "Bit depth (" << mapBitDepthEnumToString(dstBitDepth) << ") not recognized by the plugin." ); 00510 break; 00511 } 00512 } 00513 } 00514 else 00515 { 00516 TUTTLE_LOG_ERROR( "Bit depth (" << mapBitDepthEnumToString(dstBitDepth) << ") not recognized by the plugin." ); 00517 } 00518 } 00519 00520 } 00521 } 00522 }