ISIS Core Library 0.7.2 (api 3.0.0)

/scr/tee1/isis/lib/Core/DataStorage/image.cpp

Go to the documentation of this file.
00001 // kate: show-indent on; indent-mode tab; indent-width 4; tab-width 4; replace-tabs off; auto-insert-doxygen on
00002 
00003 //
00004 // C++ Implementation: image
00005 //
00006 // Description:
00007 //
00008 //
00009 // Author: Enrico Reimer<reimer@cbs.mpg.de>, (C) 2009
00010 //
00011 // Copyright: See COPYING file that comes with this distribution
00012 //
00013 //
00014 
00015 #ifdef _MSC_VER
00016 #pragma warning(disable:4996 4244)
00017 #endif
00018 
00019 #include "image.hpp"
00020 #include "../CoreUtils/vector.hpp"
00021 #include <boost/foreach.hpp>
00022 #include "../CoreUtils/property.hpp"
00023 #include <boost/token_iterator.hpp>
00024 
00025 #define _USE_MATH_DEFINES 1
00026 #include <math.h>
00027 #include <cmath>
00028 
00029 namespace isis
00030 {
00031 namespace data
00032 {
00033 
00034 ChunkOp::~ChunkOp() {}
00035 
00036 Image::Image ( ) : set( defaultChunkEqualitySet ), clean( false )
00037 {
00038     util::Singletons::get<NeededsList<Image>, 0>().applyTo( *this );
00039     set.addSecondarySort( "acquisitionNumber" );
00040 }
00041 
00042 Image::Image ( const Chunk &chunk, dimensions min_dim ) :
00043     _internal::NDimensional<4>(), util::PropertyMap(), minIndexingDim( min_dim ), set( defaultChunkEqualitySet ), clean( false )
00044 {
00045     util::Singletons::get<NeededsList<Image>, 0>().applyTo( *this );
00046     set.addSecondarySort( "acquisitionNumber" );
00047 
00048     if ( ! ( insertChunk( chunk ) && reIndex() && isClean() ) ) {
00049         LOG( Runtime, error ) << "Failed to create image from single chunk.";
00050     } else if( !isValid() ) {
00051         LOG_IF( !getMissing().empty(), Debug, warning )
00052                 << "The created image is missing some properties: " << getMissing() << ". It will be invalid.";
00053     }
00054 }
00055 
00056 Image::Image( const data::Image &ref ): _internal::NDimensional<4>(), util::PropertyMap(),
00057     set( "" )/*SortedChunkList has no default constructor - lets just make an empty (and invalid) set*/
00058 {
00059     ( *this ) = ref; // set will be replaced here anyway
00060 }
00061 
00062 Image &Image::operator=( const data::Image &ref )
00063 {
00064     //deep copy bases
00065     static_cast<util::PropertyMap &>( *this ) = static_cast<const util::PropertyMap &>( ref );
00066     static_cast<_internal::NDimensional< 4 >&>( *this ) = static_cast<const _internal::NDimensional< 4 >&>( ref );
00067     //deep copy members
00068     chunkVolume = ref.chunkVolume;
00069     clean = ref.clean;
00070     set = ref.set;
00071     minIndexingDim = ref.minIndexingDim;
00072     //replace all chunks (in set) by cheap copies of them
00073     struct : public _internal::SortedChunkList::chunkPtrOperator {
00074         boost::shared_ptr< Chunk > operator()( const boost::shared_ptr< Chunk >& ptr ) {
00075             return boost::shared_ptr< Chunk > ( new Chunk( *ptr ) );
00076         }
00077     } replace;
00078     set.transform( replace );
00079     lookup = set.getLookup();
00080     return *this;
00081 }
00082 
00083 
00084 bool Image::checkMakeClean()
00085 {
00086     if ( ! clean ) {
00087         LOG( Debug, info )  << "Image is not clean. Running reIndex ...";
00088 
00089         if( !reIndex() ) {
00090             LOG( Runtime, error ) << "Reindexing failed -- undefined behavior ahead ...";
00091         }
00092     }
00093 
00094     return clean;
00095 }
00096 bool Image::isClean()const
00097 {
00098     return clean;
00099 }
00100 
00101 void Image::deduplicateProperties()
00102 {
00103     LOG_IF( lookup.empty(), Debug, error ) << "The lookup table is empty. Won't do anything.";
00104     const boost::shared_ptr<Chunk> &first = lookup[0];
00105     //@todo might fail if the image contains a prop that differs to that in the Chunks (which is equal in the chunks)
00106     util::PropertyMap common;
00107     util::PropertyMap::KeyList uniques;
00108     first->toCommonUnique( common, uniques, true );
00109 
00110     for ( size_t i = 1; i < lookup.size(); i++ ) {
00111         lookup[i]->toCommonUnique( common, uniques, false );
00112     }
00113 
00114     LOG( Debug, info ) << uniques.size() << " Chunk-unique properties found in the Image";
00115     LOG_IF( uniques.size(), Debug, verbose_info ) << util::listToString( uniques.begin(), uniques.end(), ", " );
00116 
00117     // list should not be unified - they belong into their chunk even if they are common
00118     common.remove( common.findLists() );
00119 
00120     join( common );
00121     LOG_IF( ! common.isEmpty(), Debug, verbose_info ) << "common properties saved into the image " << common;
00122 
00123     //remove common props from the chunks
00124     for ( size_t i = 0; i != lookup.size(); i++ )
00125         lookup[i]->remove( common, false ); //this _won't keep needed properties - so from here on the chunks of the image are invalid
00126 
00127     LOG_IF( ! common.isEmpty(), Debug, verbose_info ) << "common properties removed from " << lookup.size() << " chunks: " << common;
00128 
00129 }
00130 
00131 bool Image::insertChunk ( const Chunk &chunk )
00132 {
00133     if ( chunk.getVolume() == 0 ) {
00134         LOG( Runtime, error )
00135                 << "Cannot insert empty Chunk (Size is " << chunk.getSizeAsString() << ").";
00136         return false;
00137     }
00138 
00139     if ( ! chunk.isValid() ) {
00140         LOG( Runtime, error )
00141                 << "Cannot insert invalid chunk. Missing properties: " << chunk.getMissing();
00142         return false;
00143     }
00144 
00145     if( clean ) {
00146         LOG( Runtime, warning ) << "Inserting into already indexed images is inefficient. You should not do that.";
00147 
00148         // re-gather all properties of the chunks from the image
00149         BOOST_FOREACH( boost::shared_ptr<Chunk> &ref, lookup ) {
00150             ref->join( *this );
00151         }
00152     }
00153 
00154 
00155     if( set.insert( chunk ) ) { // if the insertion was successful the image has to be reindexed anyway
00156         clean = false;
00157         lookup.clear();
00158         return true;
00159     } else {
00160         // if the insertion failed but the image was clean - de-duplicate properties again
00161         // the image is still clean - no need reindex
00162         if( clean )
00163             deduplicateProperties();
00164 
00165         return false;
00166     }
00167 }
00168 
00169 void Image::setIndexingDim( dimensions d )
00170 {
00171     minIndexingDim = d;
00172 
00173     if( clean ) {
00174         LOG( Debug, warning ) << "Image was allready indexed. reIndexing ...";
00175         reIndex();
00176     }
00177 }
00178 
00179 util::fvector3 Image::getPhysicalCoordsFromIndex( const isis::util::ivector4 &voxelCoords ) const
00180 {
00181     return  util::fvector3( voxelCoords[0] * m_RowVec[0] + voxelCoords[1] * m_ColumnVec[0] + voxelCoords[2] * m_SliceVec[0],
00182                             voxelCoords[0] * m_RowVec[1] + voxelCoords[1] * m_ColumnVec[1] + voxelCoords[2] * m_SliceVec[1],
00183                             voxelCoords[0] * m_RowVec[2] + voxelCoords[1] * m_ColumnVec[2] + voxelCoords[2] * m_SliceVec[2] )
00184             + m_Offset ;
00185 }
00186 
00187 
00188 
00189 
00190 util::ivector4 Image::getIndexFromPhysicalCoords( const isis::util::fvector3 &physicalCoords ) const
00191 {
00192     const util::fvector3 vec1 = physicalCoords - m_Offset;
00193     util::fvector4 _ret = util::fvector4( vec1[0] * m_RowVecInv[0] + vec1[1] * m_ColumnVecInv[0] + vec1[2] * m_SliceVecInv[0],
00194                                           vec1[0] * m_RowVecInv[1] + vec1[1] * m_ColumnVecInv[1] + vec1[2] * m_SliceVecInv[1],
00195                                           vec1[0] * m_RowVecInv[2] + vec1[1] * m_ColumnVecInv[2] + vec1[2] * m_SliceVecInv[2] );
00196 
00197     for( uint8_t i = 0; i < 3; i++ ) {
00198         if( _ret[i] < 0 ) _ret[i] -= 0.5;
00199         else _ret[i] += 0.5;
00200     }
00201 
00202     return _ret;
00203 }
00204 
00205 
00206 bool Image::updateOrientationMatrices()
00207 {
00208     const util::fvector3 rowVec = getPropertyAs<util::fvector3>( "rowVec" );
00209     const util::fvector3 columnVec = getPropertyAs<util::fvector3>( "columnVec" );
00210     const util::fvector3 sliceVec = getPropertyAs<util::fvector3>( "sliceVec" );
00211     m_Offset = getPropertyAs<util::fvector3>( "indexOrigin" );
00212     util::fvector3 spacing = getPropertyAs<util::fvector3>( "voxelSize" ) + ( hasProperty( "voxelGap" ) ? getPropertyAs<util::fvector3>( "voxelGap" ) : util::fvector3( 0, 0, 0 ) );
00213 
00214     for( unsigned short i = 0; i < 3; i++ ) {
00215         if( spacing[i] == 0 ) spacing[i] = 1;
00216     }
00217 
00218     m_RowVec = util::fvector3( rowVec[0] * spacing[0], rowVec[1] * spacing[0], rowVec[2] * spacing[0] );
00219     m_ColumnVec = util::fvector3( columnVec[0] * spacing[1], columnVec[1] * spacing[1], columnVec[2] * spacing[1] );
00220     m_SliceVec = util::fvector3( sliceVec[0] * spacing[2], sliceVec[1] * spacing[2], sliceVec[2] * spacing[2] );
00221     LOG( Debug, verbose_info ) << "Created orientation matrix: ";
00222     LOG( Debug, verbose_info ) << "[ " << m_RowVec[0] << " " << m_ColumnVec[0] << " " << m_SliceVec[0] << " ] + " << m_Offset[0];
00223     LOG( Debug, verbose_info ) << "[ " << m_RowVec[1] << " " << m_ColumnVec[1] << " " << m_SliceVec[1] << " ] + " << m_Offset[1];
00224     LOG( Debug, verbose_info ) << "[ " << m_RowVec[2] << " " << m_ColumnVec[2] << " " << m_SliceVec[2] << " ] + " << m_Offset[2];
00225 
00226     //since we do not want to calculate the inverse matrix with every getVoxelCoords call again we do it here once
00227     for ( size_t i = 0; i < 3; i++ ) {
00228         spacing[i] = spacing[i] ? 1.0 / spacing[i] : 0;
00229     }
00230 
00231     //for inversion of the orientation we use boost::ublas
00232     using namespace boost::numeric::ublas;
00233     matrix<float> orientation = matrix<float>( 3, 3 );
00234     matrix<float> inverse = matrix<float>( 3, 3 );
00235 
00236     for( size_t i = 0; i < 3; i++ ) {
00237         orientation( i, 0 ) = m_RowVec[i];
00238         orientation( i, 1 ) = m_ColumnVec[i];
00239         orientation( i, 2 ) = m_SliceVec[i];
00240     }
00241 
00242     if( !_internal::inverseMatrix<float>( orientation, inverse ) ) {
00243         LOG( Runtime, error ) << "Could not create the inverse of the orientation matrix!";
00244         return false;
00245     };
00246 
00247     for( size_t i = 0; i < 3; i++ ) {
00248         m_RowVecInv[i] = inverse( i, 0 );
00249         m_ColumnVecInv[i] = inverse( i, 1 );
00250         m_SliceVecInv[i] = inverse( i, 2 );
00251     }
00252 
00253     LOG( Debug, verbose_info ) << "Created transposed orientation matrix: ";
00254     LOG( Debug, verbose_info ) << "[ " << m_RowVecInv[0] << " " << m_ColumnVecInv[0] << " " << m_SliceVecInv[0] << " ] + " << m_Offset[0];
00255     LOG( Debug, verbose_info ) << "[ " << m_RowVecInv[1] << " " << m_ColumnVecInv[1] << " " << m_SliceVecInv[1] << " ] + " << m_Offset[1];
00256     LOG( Debug, verbose_info ) << "[ " << m_RowVecInv[2] << " " << m_ColumnVecInv[2] << " " << m_SliceVecInv[2] << " ] + " << m_Offset[2];
00257     return true;
00258 }
00259 
00260 bool Image::transformCoords( boost::numeric::ublas::matrix< float > transform_matrix, bool transformCenterIsImageCenter )
00261 {
00262     //for transforming we have to ensure to have the below properties in our chunks and image
00263     static const char  *neededProps[] = {"indexOrigin", "rowVec", "columnVec", "sliceVec", "voxelSize"};
00264     //propagate needed properties to chunks
00265     std::set<PropPath> propPathList;
00266     BOOST_FOREACH ( const char * prop, neededProps ) {
00267         const util::PropertyMap::PropPath pPath( prop );
00268 
00269         if( hasProperty ( pPath ) ) {
00270             const util::fvector3 p = getPropertyAs<util::fvector3> ( pPath );
00271             BOOST_FOREACH ( std::vector<boost::shared_ptr< data::Chunk> >::reference chRef, lookup ) {
00272                 if ( !chRef->hasProperty ( pPath ) ) {
00273                     chRef->setPropertyAs<util::fvector3> ( pPath, p );
00274                     propPathList.insert( pPath );
00275                 }
00276             }
00277         } else {
00278             LOG( Runtime, error ) << "Cannot do transformCoords on image without " << prop;
00279             return false;
00280         }
00281     }
00282 
00283     BOOST_FOREACH ( std::vector<boost::shared_ptr< data::Chunk> >::reference chRef, lookup ) {
00284         if ( !chRef->transformCoords ( transform_matrix, transformCenterIsImageCenter ) ) {
00285             return false;
00286         }
00287 
00288         BOOST_FOREACH( std::list<PropPath>::const_reference pPathNotNeeded, propPathList ) {
00289             chRef->remove( pPathNotNeeded );
00290         }
00291     }
00292     //      establish initial state
00293 
00294     if ( !isis::data::_internal::transformCoords ( *this, getSizeAsVector(), transform_matrix, transformCenterIsImageCenter ) ) {
00295         LOG ( Runtime, error ) << "Error during transforming the coords of the image.";
00296         return false;
00297     }
00298 
00299     if ( !updateOrientationMatrices() ) {
00300         LOG ( Runtime, error ) << "Could not update the orientation matrices of the image!";
00301         return false;
00302     }
00303 
00304     return true;
00305 }
00306 
00307 dimensions Image::mapScannerAxisToImageDimension( scannerAxis scannerAxes )
00308 {
00309     updateOrientationMatrices();
00310     boost::numeric::ublas::matrix<float> latchedOrientation = boost::numeric::ublas::zero_matrix<float>( 4, 4 );
00311     boost::numeric::ublas::vector<float>mapping( 4 );
00312     latchedOrientation( m_RowVec.getBiggestVecElemAbs(), 0 ) = 1;
00313     latchedOrientation( m_ColumnVec.getBiggestVecElemAbs(), 1 ) = 1;
00314     latchedOrientation( m_SliceVec.getBiggestVecElemAbs(), 2 ) = 1;
00315     latchedOrientation( 3, 3 ) = 1;
00316 
00317     for( size_t i = 0; i < 4; i++ ) {
00318         mapping( i ) = i;
00319     }
00320 
00321     return static_cast<dimensions>( boost::numeric::ublas::prod( latchedOrientation, mapping )( scannerAxes ) );
00322 
00323 }
00324 
00325 
00326 bool Image::reIndex()
00327 {
00328     if ( set.isEmpty() ) {
00329         LOG( Debug, warning ) << "Reindexing an empty image is useless.";
00330         return false;
00331     }
00332 
00333     if( !set.isRectangular() ) {
00334         LOG( Runtime, error ) << "The image is incomplete. Aborting reindex. (geometric size is " << set.getHorizontalSize() << ")";
00335         return false;
00336     }
00337 
00338     //redo lookup table
00339     lookup = set.getLookup();
00340     util::FixedVector<size_t, dims> structure_size; //storage for the size of the chunk structure
00341     structure_size.fill( 1 );
00342     //get primary attributes from geometrically first chunk - will be usefull
00343     const Chunk &first = chunkAt( 0 );
00344     //start indexing at eigther the chunk-size or the givem minIndexingDim (whichever is bigger)
00345     const unsigned short chunk_dims = std::max<unsigned short>( first.getRelevantDims(), minIndexingDim );
00346     chunkVolume = first.getVolume();
00348     // Determine structure of the image by searching for dimensional breaks in the chunklist
00350     //get indexOrigin from the geometrically first chunk
00351     propertyValue( "indexOrigin" ) = first.propertyValue( "indexOrigin" );
00352     //if there are many chunks, they must leave at least on dimension to the image to "sort" them in
00353     const size_t timesteps = set.getHorizontalSize();
00354     const unsigned short sortDims = dims - ( timesteps > 1 ? 1 : 0 ); // dont use the uppermost dim, if the timesteps are already there
00355 
00356     if ( chunk_dims >= Image::dims ) {
00357         if ( lookup.size() > 1 ) {
00358             LOG( Runtime, error )
00359                     << "Cannot handle multiple Chunks, if they have more than "
00360                     << Image::dims - 1 << " dimensions";
00361             return false;
00362         }
00363 
00364         //if there is only one chunk, its ok - the image will consist only of this one,
00365         //and commonGet will allways return <0,set.begin()->getLinearIndex()>
00366         //thus in this case voxel() equals set.begin()->voxel()
00367     } else {// OK there is at least one dimension to sort in the chunks
00368         LOG( Debug, info ) << "Computing strides for dimensions " << util::MSubject( chunk_dims + 1 ) << " to " << util::MSubject( sortDims );
00369 
00370         // check the chunks for at least one dimensional break - use that for the size of that dimension
00371         for ( unsigned short i = chunk_dims; i < sortDims; i++ ) { //if there are dimensions left figure out their size
00372             structure_size[i] = getChunkStride( structure_size.product() ) / structure_size.product();
00373             assert( structure_size[i] != 0 );
00374         }
00375     }
00376 
00377     if ( sortDims < dims ) { //if there is a timedim (not all dims was used for geometric sort)
00378         assert( structure_size[sortDims] == 1 );
00379         structure_size[sortDims] = timesteps; // fill the dim above the top geometric dim with the timesteps
00380     }
00381 
00382     assert( structure_size.product() == lookup.size() );
00383     //Clean up the properties
00384     deduplicateProperties();
00385 
00386     // add the chunk-size to the image-size
00387     for ( unsigned short i = 0; i < chunk_dims; i++ )
00388         structure_size[i] = first.getDimSize( i );
00389 
00390     init( structure_size ); // set size of the image
00392     //reconstruct some redundant information, if its missing
00394     const util::PropertyMap::KeyType vectors[] = {"rowVec", "columnVec", "sliceVec"};
00395     int oneCnt = 0;
00396     BOOST_FOREACH( const util::PropertyMap::KeyType & ref, vectors ) {
00397         if ( hasProperty( ref ) ) {
00398             util::PropertyValue &prop = propertyValue( ref );
00399             LOG_IF( !prop.is<util::fvector3>(), Debug, error ) << "Using " << prop.getTypeName() << " as " << util::Value<util::fvector3>::staticName();
00400             util::fvector3 &vec = prop.castTo<util::fvector3>();
00401 
00402             if( vec.sqlen() == 0 ) {
00403                 util::fvector3  v_one;
00404                 v_one[oneCnt] = 1;
00405                 LOG( Runtime, error )
00406                         << "The existing " << ref << " " << vec << ( hasProperty( "source" ) ? " from " + getPropertyAs<std::string>( "source" ) : "" ) << " has the length zero. Falling back to " << v_one << ".";
00407                 vec = v_one;
00408             }
00409 
00410             vec.norm();
00411         }
00412 
00413         oneCnt++;
00414     }
00415 
00416     util::fvector3 &voxeSize = propertyValue( "voxelSize" ).castTo<util::fvector3>();
00417 
00418     for( int i = 0; i < 3; i++ ) {
00419         if( voxeSize[i] == 0 || std::isinf( voxeSize[i] ) ) {
00420                 LOG( Runtime, warning ) << "voxelSize[" << i << "]=="  << voxeSize[i] << " is invalid, using 1";
00421                 voxeSize[i] = 1;
00422             }
00423         }
00424 
00425 
00426         //if we have at least two slides (and have slides (with different positions) at all)
00427         if ( chunk_dims == 2 && structure_size[2] > 1 && first.hasProperty( "indexOrigin" ) ) {
00428             const util::fvector3 thisV = first.getPropertyAs<util::fvector3>( "indexOrigin" );
00429             const Chunk &last = chunkAt( structure_size[2] - 1 );
00430 
00431             if ( last.hasProperty( "indexOrigin" ) ) {
00432                 const util::fvector3 lastV = last.getPropertyAs<util::fvector3>( "indexOrigin" );
00433                 //check the slice vector
00434                 util::fvector3 distVecNorm = lastV - thisV;
00435                 LOG_IF( distVecNorm.len() == 0, Runtime, error )
00436                 << "The distance between the the first and the last chunk is zero. Thats bad, because I'm going to normalize it.";
00437                 distVecNorm.norm();
00438 
00439                 if ( hasProperty( "sliceVec" ) ) {
00440                     const util::fvector3 sliceVec = getPropertyAs<util::fvector3>( "sliceVec" );
00441                     LOG_IF( ! distVecNorm.fuzzyEqual( sliceVec ), Runtime, info )
00442                     << "The existing sliceVec " << sliceVec
00443                     << " differs from the distance vector between chunk 0 and " << structure_size[2] - 1
00444                     << " " << distVecNorm;
00445                 } else {
00446                     LOG( Debug, info )
00447                     << "used the distance between chunk 0 and " << structure_size[2] - 1
00448                     << " to synthesize the missing sliceVec as " << distVecNorm;
00449                     propertyValue( "sliceVec" ) = distVecNorm;
00450                 }
00451             }
00452 
00453             const Chunk &next = chunkAt( 1 );
00454 
00455             if ( next.hasProperty( "indexOrigin" ) ) {
00456                 const util::fvector3 nextV = next.getPropertyAs<util::fvector3>( "indexOrigin" );
00457                 const float sliceDist = ( nextV - thisV ).len() - voxeSize[2];
00458 
00459                 if ( sliceDist > 0 ) {
00460                     const float inf = std::numeric_limits<float>::infinity();
00461 
00462                     if ( ! hasProperty( "voxelGap" ) ) { // @todo check this
00463                         setPropertyAs( "voxelGap", util::fvector3( 0, 0, inf ) );
00464                     }
00465 
00466                     util::fvector3 &voxelGap = propertyValue( "voxelGap" ).castTo<util::fvector3>(); //if there is no voxelGap yet, we create it
00467 
00468                     if ( voxelGap[2] != inf ) {
00469                         if ( ! util::fuzzyEqual( voxelGap[2], sliceDist ) ) {
00470                             LOG_IF( ! util::fuzzyEqual( voxelGap[2], sliceDist ), Runtime, warning )
00471                             << "The existing slice distance (voxelGap[2]) " << util::MSubject( voxelGap[2] )
00472                             << " differs from the distance between chunk 0 and 1, which is " << sliceDist;
00473                         }
00474                     } else {
00475                         voxelGap[2] = sliceDist;
00476                         LOG( Debug, info )
00477                         << "used the distance between chunk 0 and 1 to synthesize the missing slice distance (voxelGap[2]) as "
00478                         << sliceDist;
00479                     }
00480                 }
00481             }
00482         }
00483 
00484         //if we have row- and column- vector
00485         if ( hasProperty( "rowVec" ) && hasProperty( "columnVec" ) ) {
00486             util::fvector3 &row = propertyValue( "rowVec" ).castTo<util::fvector3>();
00487             util::fvector3 &column = propertyValue( "columnVec" ).castTo<util::fvector3>();
00488             LOG_IF( row.dot( column ) > 0.01, Runtime, warning ) << "The cosine between the columns and the rows of the image is bigger than 0.01";
00489             const util::fvector3 crossVec = util::fvector3( //we could use their cross-product as sliceVector
00490                                                 row[1] * column[2] - row[2] * column[1],
00491                                                 row[2] * column[0] - row[0] * column[2],
00492                                                 row[0] * column[1] - row[1] * column[0]
00493                                             );
00494 
00495             if ( hasProperty( "sliceVec" ) ) {
00496                 util::fvector3 &sliceVec = propertyValue( "sliceVec" ).castTo<util::fvector3>(); //get the slice vector
00497                 LOG_IF( std::acos( crossVec.dot( sliceVec ) )  > 180 / M_PI, Runtime, warning ) //angle more than one degree
00498                 << "The existing sliceVec " << sliceVec
00499                 << " differs from the cross product of the row- and column vector " << crossVec;
00500             } else {
00501                 // We dont know anything about the slice-direction
00502                 // we just guess its along the positive cross-product between row- and column direction
00503                 // so at least warn the user if we do that long shot
00504                 LOG( Runtime, info )
00505                 << "used the cross product between rowVec and columnVec as sliceVec:"
00506                 << crossVec << ". That might be wrong!";
00507                 setPropertyAs( "sliceVec", crossVec );
00508             }
00509         }
00510 
00511         if ( hasProperty( "fov" ) ) {
00512             util::fvector3 &propFoV = propertyValue( "fov" ).castTo<util::fvector3>();
00513 
00514             const util::fvector3 &calcFoV = getFoV();
00515 
00516             bool ok = true;
00517 
00518             for ( size_t i = 0; i < 3; i++ ) {
00519                 if ( propFoV[i] != -std::numeric_limits<float>::infinity() ) {
00520                     ok &= util::fuzzyEqual( propFoV[i], calcFoV[i] );
00521                 } else
00522                     propFoV[i] = calcFoV[i];
00523             }
00524 
00525             LOG_IF( ! ok, Runtime, info )
00526             << "The calculated field of view differs from the stored " << propFoV << "/" << calcFoV;
00527         }
00528 
00529         LOG_IF( ! isValid(), Runtime, warning ) << "The image is not valid after reindexing. Missing properties: " << getMissing();
00530 
00531         // check if there is a list in any chunk
00532         bool found = false;
00533 
00534         for ( size_t i = 0; i < lookup.size() && found == false; i++ ) {
00535             const KeyList lists_list = lookup[i]->findLists();
00536             LOG_IF( !lists_list.empty(), Debug, info ) << "Found property-lists " << util::MSubject( lists_list ) << " in chunk number " << i << " going to splice the image";
00537             found = !lists_list.empty();
00538         }
00539 
00540         if( found ) { // splice down the image one step if there are some
00541             const size_t relDims = lookup[0]->getRelevantDims();
00542             assert( relDims > 1 );
00543             spliceDownTo( static_cast<data::dimensions>( relDims - 1 ) );
00544         }
00545 
00546 
00547         updateOrientationMatrices();
00548         return clean = isValid();
00549     }
00550 
00551     bool Image::isEmpty()const {
00552         return set.isEmpty();
00553     }
00554 
00555     const boost::shared_ptr< Chunk >& Image::chunkPtrAt( size_t pos )const {
00556         LOG_IF( lookup.empty(), Debug, error ) << "The lookup table is empty. Run reIndex first.";
00557         LOG_IF( pos >= lookup.size(), Debug, error ) << "Index is out of the range of the lookup table (" << pos << ">=" << lookup.size() << ").";
00558         const boost::shared_ptr<Chunk> &ptr = lookup[pos];
00559         LOG_IF( !ptr, Debug, error ) << "There is no chunk at " << pos << ". This usually happens in incomplete images.";
00560         return ptr;
00561     }
00562 
00563     Chunk Image::getChunkAt( size_t pos, bool copy_metadata )const {
00564         Chunk ret( *chunkPtrAt( pos ) );
00565 
00566         if( copy_metadata )ret.join( *this ); // copy all metadata from the image in here
00567 
00568         return ret;
00569     }
00570     Chunk &Image::chunkAt( size_t pos ) {
00571         return *chunkPtrAt( pos );
00572     }
00573 
00574     Chunk Image::getChunk ( size_t first, size_t second, size_t third, size_t fourth, bool copy_metadata ) {
00575         checkMakeClean();
00576         return const_cast<const Image &>( *this ).getChunk( first, second, third, fourth, copy_metadata ); // use the const version
00577     }
00578 
00579     const Chunk Image::getChunk ( size_t first, size_t second, size_t third, size_t fourth, bool copy_metadata ) const {
00580         const size_t index = commonGet( first, second, third, fourth ).first;
00581         return getChunkAt( index, copy_metadata );
00582     }
00583 
00584     void Image::copyToValueArray( ValueArrayBase & dst, scaling_pair scaling ) const {
00585         if( getVolume() > dst.getLength() ) {
00586             LOG( Runtime, error ) << "Image wont fit into the ValueArray, wont copy..";
00587             return;
00588         }
00589 
00590         if ( clean ) {
00591             if ( scaling.first.isEmpty() || scaling.second.isEmpty() ) {
00592                 scaling = getScalingTo ( dst.getTypeID() );
00593             }
00594 
00595             std::vector< ValueArrayReference > targets;
00596 
00597             if( lookup.size() > 1 ) { //if there are more than 1 chunks
00598                 //splice target to have the same parts as the image
00599                 targets = dst.splice( lookup.front()->getVolume() );
00600             } else {
00601                 //just put that ValueArray into the list
00602                 targets.push_back( dst );
00603             }
00604 
00605             std::vector< ValueArrayReference >::iterator target = targets.begin();
00606             BOOST_FOREACH ( const boost::shared_ptr<Chunk> &ref, lookup ) { // copy chunks into the parts
00607                 if ( !ref->getValueArrayBase().copyTo ( **target, scaling ) ) {
00608                     LOG ( Runtime, error )
00609                             << "Failed to copy raw data of type " << ref->getTypeName() << " from " << getSizeAsString() << "-image into ValueArray of type "
00610                             << dst.getTypeName() << " and length " << dst.getLength();
00611                 }
00612 
00613                 target++;
00614             }
00615         } else {
00616             LOG ( Runtime, error ) << "Cannot copy from non clean images. Run reIndex first";
00617         }
00618     }
00619 
00620     Image Image::copyByID( short unsigned int ID, scaling_pair scaling ) const {
00621         Image ret( *this ); // ok we just cheap-copied the whole image
00622 
00623         //we want deep copies of the chunks, and we want them to be of type ID
00624         struct : _internal::SortedChunkList::chunkPtrOperator {
00625             std::pair<util::ValueReference, util::ValueReference> scale;
00626             unsigned short ID;
00627             boost::shared_ptr<Chunk> operator() ( const boost::shared_ptr< Chunk >& ptr ) {
00628                 return boost::shared_ptr<Chunk> ( new Chunk ( ptr->copyByID( ID, scale ) ) );
00629             }
00630         } conv_op;
00631 
00632         if( ID && ( scaling.first.isEmpty() || scaling.second.isEmpty() ) ) // if we have an ID but no scaling, compute it
00633             conv_op.scale = getScalingTo( ID );
00634 
00635         conv_op.ID = ID;
00636 
00637         ret.set.transform ( conv_op );
00638 
00639         if ( ret.isClean() ) {
00640             ret.lookup = ret.set.getLookup(); // the lookup table still points to the old chunks
00641         } else {
00642             LOG ( Debug, info ) << "Copied unclean image. Running reIndex on the copy.";
00643             ret.reIndex();
00644         }
00645 
00646         return *this;
00647 
00648     }
00649 
00650     std::vector< Chunk > Image::copyChunksToVector( bool copy_metadata )const {
00651         std::vector<isis::data::Chunk> ret;
00652         ret.reserve( lookup.size() );
00653         std::vector<boost::shared_ptr<Chunk> >::const_iterator at = lookup.begin();
00654         const std::vector<boost::shared_ptr<Chunk> >::const_iterator end = lookup.end();
00655 
00656         while ( at != end ) {
00657             ret.push_back( **( at++ ) );
00658 
00659             if( copy_metadata )
00660                 ret.back().join( *this );
00661         }
00662 
00663         return ret;
00664     }
00665 
00666     size_t Image::getChunkStride ( size_t base_stride ) {
00667         LOG_IF( set.isEmpty(), Runtime, error ) << "Trying to get chunk stride in an empty image";
00668         LOG_IF( lookup.empty(), Debug, error ) << "Lookup table for chunks is empty. Do reIndex() first!";
00669 
00670         if ( lookup.size() >= 4 * base_stride ) {
00671             /* there can't be any stride with less than 3*base_stride chunks (which would actually be an invalid image)
00672              * _____
00673              * |c c| has no stride/dimensional break
00674              * _____
00675              * |c c|
00676              * |c  | has a dimensional break, but is invalid
00677              * _____
00678              * |c c|
00679              * |c c| is the first reasonable case
00680              */
00681             // get the distance between first and second chunk for comparision
00682             const util::fvector3 firstV = chunkAt( 0 ).getPropertyAs<util::fvector3>( "indexOrigin" );
00683             const util::fvector3 secondV = chunkAt( base_stride ).getPropertyAs<util::fvector3>( "indexOrigin" );
00684             const util::fvector3 dist1 = secondV - firstV;
00685 
00686             if( dist1.sqlen() == 0 ) { //if there is no geometric structure anymore - so asume its flat from here on
00687                 LOG( Debug, info ) << "Distance between 0 and " << util::MSubject( base_stride )
00688                                    << " is zero. Assuming there are no dimensional breaks anymore. Returning " << util::MSubject( base_stride );
00689                 return base_stride;
00690             } else for ( size_t i = base_stride; i < lookup.size() - base_stride; i += base_stride ) {  // compare every follwing distance to that
00691                     const util::fvector3 thisV = chunkAt( i ).getPropertyAs<util::fvector3>( "indexOrigin" );
00692                     const util::fvector3 nextV = chunkAt( i + base_stride ).getPropertyAs<util::fvector3>( "indexOrigin" );
00693                     const util::fvector3 distFirst = nextV - firstV;
00694                     const util::fvector3 distThis = nextV - thisV;
00695                     LOG( Debug, verbose_info )
00696                             << "Distance between chunk " << util::MSubject( i ) << " and " << util::MSubject( i + base_stride )
00697                             << " is " << distThis.len() << ". Distance between 0 and " << util::MSubject( i + base_stride ) << " is " << distFirst.len();
00698 
00699                     if ( distFirst.sqlen() <= distThis.sqlen() ) { // the next chunk is nearer to the begin than to this => dimensional break => leave
00700                         LOG( Debug, info )
00701                                 << "Distance between chunk " << util::MSubject( i + base_stride )
00702                                 << " and 0 is not bigger than the distance between " << util::MSubject( i + base_stride )
00703                                 << " and " << util::MSubject( i ) << ", assuming dimensional break at " << i + base_stride;
00704                         return i + base_stride;
00705                     }
00706                 }
00707         } else  if ( lookup.size() % base_stride ) {
00708             LOG( Runtime, error )
00709                     << "The amount of chunks (" << lookup.size()
00710                     << ") is not divisible by the block size of the dimension below (" << base_stride
00711                     << "). Maybe the image is incomplete.";
00712             LOG( Runtime, warning )
00713                     << "Ignoring "  <<  lookup.size() % base_stride << " chunks.";
00714             return lookup.size() - ( lookup.size() % base_stride );
00715         }
00716 
00717         //we didn't find any break, so we assume its a linear image |c c ... c|
00718         LOG( Debug, info )
00719                 << "No dimensional break found, assuming it to be at the end (" << lookup.size() << "/" << set.getHorizontalSize() << ")";
00720         return lookup.size() / set.getHorizontalSize();
00721     }
00722 
00723     std::list<util::PropertyValue> Image::getChunksProperties( const util::PropertyMap::KeyType & key, bool unique )const {
00724         std::list<util::PropertyValue > ret;
00725 
00726         if( clean ) {
00727             BOOST_FOREACH( const boost::shared_ptr<Chunk> &ref, lookup ) {
00728                 const util::PropertyValue &prop = ref->propertyValue( key );
00729 
00730                 if ( unique && prop.isEmpty() ) //if unique is requested and the property is empty
00731                     continue; //skip it
00732                 else if ( unique && !ret.empty() &&  prop == ret.back() )
00733                     //if unique is requested and the property is equal to the one added before
00734                     continue;//skip it
00735                 else
00736                     ret.push_back( prop );
00737             }
00738         } else {
00739             LOG( Runtime, error ) << "Cannot get chunk-properties from non clean images. Run reIndex first";
00740         }
00741 
00742         return ret;
00743     }
00744 
00745     size_t Image::getMaxBytesPerVoxel() const {
00746         size_t bytes = chunkPtrAt( 0 )->getBytesPerVoxel();
00747         BOOST_FOREACH( const boost::shared_ptr<Chunk> &ref, lookup ) {
00748             LOG_IF( bytes != ref->getBytesPerVoxel(), Debug, warning )
00749                     << "Not all voxels have the same byte size (" << bytes << "!=" << ref->getBytesPerVoxel() << "). Using the biggest.";
00750 
00751             if( bytes < ref->getBytesPerVoxel() ) {
00752                 bytes = ref->getBytesPerVoxel();
00753             }
00754         }
00755         return bytes;
00756     }
00757 
00758     std::pair<util::ValueReference, util::ValueReference> Image::getMinMax () const {
00759         std::pair<util::ValueReference, util::ValueReference> ret;
00760 
00761         if( !lookup.empty() ) {
00762             std::vector<boost::shared_ptr<Chunk> >::const_iterator i = lookup.begin();
00763             ret = ( *i )->getMinMax();
00764 
00765             for( ++i; i != lookup.end(); ++i ) {
00766                 std::pair<util::ValueReference, util::ValueReference> current = ( *i )->getMinMax();
00767 
00768                 if( ret.first->gt( *current.first ) )
00769                     ret.first = current.first;
00770 
00771                 if( ret.second->lt( *current.second ) )
00772                     ret.second = current.second;
00773             }
00774         }
00775 
00776         return ret;
00777     }
00778 
00779     // @todo this wont work with images of more 2 two different data types
00780     std::pair< util::ValueReference, util::ValueReference > Image::getScalingTo( short unsigned int targetID, autoscaleOption scaleopt ) const {
00781         LOG_IF( !clean, Debug, error ) << "You should run reIndex before running this";
00782         std::pair<util::ValueReference, util::ValueReference> minmax = getMinMax();
00783 
00784         BOOST_FOREACH( const boost::shared_ptr<const Chunk> &ref, lookup ) { //find a chunk which would be converted
00785             if( targetID != ref->getTypeID() ) {
00786                 const scaling_pair scale = ref->getScalingTo( targetID, minmax, scaleopt );
00787                 LOG_IF( scale.first.isEmpty() || scale.second.isEmpty(), Debug, error ) << "Returning an invalid scaling. This is bad!";
00788                 return scale; // and ask that for the scaling
00789             }
00790         }
00791         return std::make_pair( //ok seems like no conversion is needed - return 1/0
00792                    util::ValueReference( util::Value<uint8_t>( 1 ) ),
00793                    util::ValueReference( util::Value<uint8_t>( 0 ) )
00794                );
00795     }
00796 
00797     size_t Image::compare( const isis::data::Image & comp ) const {
00798         size_t ret = 0;
00799         LOG_IF( ! ( clean && comp.clean ), Debug, error )
00800                 << "Comparing unindexed images will cause you trouble, run reIndex()!";
00801 
00802         if ( getSizeAsVector() != comp.getSizeAsVector() ) {
00803             LOG( Runtime, warning ) << "Size of images differs (" << getSizeAsVector() << "/"
00804                                     << comp.getSizeAsVector() << "). Adding difference to the result.";
00805             ret += ( getSizeAsVector() - comp.getSizeAsVector() ).product();
00806         }
00807 
00808         util::ivector4 compVect( util::minVector( chunkPtrAt( 0 )->getSizeAsVector(), comp.chunkPtrAt( 0 )->getSizeAsVector() ) );
00809         util::ivector4 start;
00810         const size_t increment = compVect.product();
00811 
00812         for ( size_t i = 0; i < getVolume(); i += increment ) {
00813             const size_t nexti = i + increment - 1;
00814             const std::pair<size_t, size_t> c1pair1( i / chunkVolume, i % chunkVolume );
00815             const std::pair<size_t, size_t> c1pair2( nexti / chunkVolume, nexti % chunkVolume );
00816             const std::pair<size_t, size_t> c2pair1( i / comp.chunkVolume, i % comp.chunkVolume );
00817             assert( c1pair1.first == c1pair2.first );
00818             LOG( Debug, verbose_info ) << "Comparing chunks at " << c1pair1.first << " and "   << c2pair1.first;
00819             const Chunk &c1 = *chunkPtrAt( c1pair1.first );
00820             const Chunk &c2 = *( comp.chunkPtrAt( c2pair1.first ) );
00821             LOG( Debug, verbose_info )
00822                     << "Start positions are " << c1pair1.second << " and " << c2pair1.second
00823                     << " and the length is " << c1pair2.second - c1pair1.second;
00824             ret += c1.getValueArrayBase().compare( c1pair1.second, c1pair2.second, c2.getValueArrayBase(), c2pair1.second );
00825         }
00826 
00827         return ret;
00828     }
00829 
00830     Image::orientation Image::getMainOrientation()const {
00831         LOG_IF( ! isValid() || ! clean, Debug, warning ) << "You should not run this on non clean image. Run reIndex first.";
00832         util::fvector3 row = getPropertyAs<util::fvector3>( "rowVec" );
00833         util::fvector3 column = getPropertyAs<util::fvector3>( "columnVec" );
00834         row.norm();
00835         column.norm();
00836         LOG_IF( row.dot( column ) > 0.01, Runtime, warning ) << "The cosine between the columns and the rows of the image is bigger than 0.01";
00837         const util::fvector3 crossVec = util::fvector3(
00838                                             row[1] * column[2] - row[2] * column[1],
00839                                             row[2] * column[0] - row[0] * column[2],
00840                                             row[0] * column[1] - row[1] * column[0]
00841                                         );
00842         const util::fvector3 x( 1, 0 ), y( 0, 1 ), z( 0, 0, 1 );
00843         double a_axial    = std::acos( crossVec.dot( z ) ) / M_PI;
00844         double a_sagittal = std::acos( crossVec.dot( x ) ) / M_PI;
00845         double a_coronal  = std::acos( crossVec.dot( y ) ) / M_PI;
00846         bool a_inverse = false, s_inverse = false, c_inverse = false;
00847         LOG( Debug, info ) << "Angles to vectors are " << ( a_sagittal * 180 ) << " to x, " << ( a_coronal * 180 ) << " to y and " << ( a_axial * 180 ) << " to z";
00848 
00849         if( a_axial > .5 ) {
00850             a_axial = std::abs( a_axial - 1 );
00851             a_inverse = true;
00852         }
00853 
00854         if( a_sagittal > .5 ) {
00855             a_sagittal = std::abs( a_sagittal - 1 );
00856             s_inverse = true;
00857         }
00858 
00859         if( a_coronal > .5 ) {
00860             a_coronal = std::abs( a_coronal - 1 );
00861             c_inverse = true;
00862         }
00863 
00864         if( a_axial <= .25 )
00865             return a_inverse ? reversed_axial : axial;
00866         else if( a_sagittal <= .25 )
00867             return s_inverse ? reversed_sagittal : sagittal;
00868         else if( a_coronal <= .25 )
00869             return c_inverse ? reversed_coronal : coronal;
00870         else
00871             assert( false );
00872 
00873         return axial; //will never be reached
00874     }
00875 
00876     unsigned short Image::getMajorTypeID() const {
00877         switch( getChunk( 0 ).getTypeID() ) { // dont do smart typeID detection for types who cant do minmax
00878         case data::ValueArray<util::color24>::staticID:
00879         case data::ValueArray<util::color48>::staticID:
00880         case data::ValueArray<std::complex< float >  >::staticID:
00881         case data::ValueArray<std::complex< double > >::staticID:
00882             LOG( Debug, info ) << "Using flat typeID for " << getChunk( 0 ).getTypeName() << " because I cannot compute min/max";
00883             return getChunk( 0 ).getTypeID();
00884             break;
00885         default:
00886             std::pair<util::ValueReference, util::ValueReference> minmax = getMinMax();
00887             LOG( Debug, info ) << "Determining  datatype of image with the value range " << minmax;
00888 
00889             if( minmax.first->getTypeID() == minmax.second->getTypeID() ) { // ok min and max are the same type - trivial case
00890                 return minmax.first->getTypeID() << 8; // btw: we do the shift, because min and max are Value - but we want the ID's ValueArray
00891             } else if( minmax.first->fitsInto( minmax.second->getTypeID() ) ) { // if min fits into the type of max, use that
00892                 return minmax.second->getTypeID() << 8; //@todo maybe use a global static function here instead of a obscure shit operation
00893             } else if( minmax.second->fitsInto( minmax.first->getTypeID() ) ) { // if max fits into the type of min, use that
00894                 return minmax.first->getTypeID() << 8;
00895             } else {
00896                 LOG( Runtime, error ) << "Sorry I dont know which datatype I should use. (" << minmax.first->getTypeName() << " or " << minmax.second->getTypeName() << ")";
00897                 std::stringstream o;
00898                 o << "Type selection failed. Range was: " << minmax;
00899                 throw( std::logic_error( o.str() ) );
00900             }
00901 
00902             break;
00903         }
00904 
00905         return 0; // id 0 is invalid
00906     }
00907     std::string Image::getMajorTypeName() const {
00908         return util::getTypeMap()[getMajorTypeID()];
00909     }
00910 
00911     bool Image::convertToType( short unsigned int ID, autoscaleOption scaleopt ) {
00912         bool retVal = true;
00913         BOOST_FOREACH( boost::shared_ptr<Chunk> &ref, lookup ) {
00914             retVal &= ( ref->getTypeID() == ID );
00915         }
00916 
00917         if( retVal ) // if all chunks allready have the requested type we can skip the rest
00918             return true;
00919 
00920         // get value range of the image for the conversion
00921         scaling_pair scale = getScalingTo( ID, scaleopt );
00922 
00923         LOG( Debug, info ) << "Computed scaling of the original image data: [" << scale << "]";
00924         retVal = true;
00925         //we want all chunks to be of type ID - so tell them
00926         BOOST_FOREACH( boost::shared_ptr<Chunk> &ref, lookup ) {
00927             retVal &= ref->convertToType( ID, scale );
00928         }
00929         return retVal;
00930     }
00931 
00932     size_t Image::spliceDownTo( dimensions dim ) { //rowDim = 0, columnDim, sliceDim, timeDim
00933         if( lookup[0]->getRelevantDims() < ( size_t ) dim ) {
00934             LOG( Debug, error ) << "The dimensionality of the chunks of this image is already below " << dim << " cannot splice it.";
00935             return 0;
00936         } else if( lookup[0]->getRelevantDims() == ( size_t ) dim ) {
00937             LOG( Debug, info ) << "Skipping useless splicing, relevantDims is already " << lookup[0]->getRelevantDims();
00938             return lookup.size();
00939         }
00940 
00941         util::vector4<size_t> image_size = getSizeAsVector();
00942 
00943         for( int i = 0; i < dim; i++ )
00944             image_size[i] = 1;
00945 
00946         // get a list of needed properties (everything which is missing in a newly created chunk plus everything which is needed for autosplice)
00947         const std::list<util::PropertyMap::KeyType> splice_needed = util::stringToList<util::PropertyMap::KeyType>( util::PropertyMap::KeyType( "voxelSize,voxelGap,rowVec,columnVec,sliceVec,indexOrigin,acquisitionNumber" ), ',' );
00948         util::PropertyMap::KeyList needed = MemChunk<short>( 1 ).getMissing();
00949         needed.insert( splice_needed.begin(), splice_needed.end() );
00950         struct splicer {
00951             dimensions m_dim;
00952             Image &m_image;
00953             size_t m_amount;
00954             splicer( dimensions dimemsion, size_t amount, Image &image ): m_dim( dimemsion ), m_image( image ), m_amount( amount ) {}
00955             void operator()( const Chunk &ch ) {
00956                 const size_t topDim = ch.getRelevantDims() - 1;
00957 
00958                 if( topDim >= ( size_t ) m_dim ) { // ok we still have to splice that
00959                     const size_t subSize = m_image.getSizeAsVector()[topDim];
00960                     assert( !( m_amount % subSize ) ); // there must not be any "remaining"
00961                     splicer sub( m_dim, m_amount / subSize, m_image );
00962                     BOOST_FOREACH( const Chunk & ref, ch.autoSplice( uint32_t( m_amount / subSize ) ) ) {
00963                         sub( ref );
00964                     }
00965                 } else { // seems like we're done - insert it into the image
00966                     assert( ch.getRelevantDims() == ( size_t ) m_dim ); // index of the higest dim>1 (ch.getRelevantDims()-1) shall be equal to the dim below the requested splicing (m_dim-1)
00967                     LOG( Debug, verbose_info ) << "Inserting splice result of size " << ch.getSizeAsVector() << " at " << ch.propertyValue( "indexOrigin" );
00968                     m_image.insertChunk( ch );
00969                 }
00970             }
00971         };
00972         std::vector<boost::shared_ptr<Chunk> > buffer = lookup; // store the old lookup table
00973         lookup.clear();
00974         set.clear(); // clear the image, so we can insert the splices
00975         clean = false; // mark the image for reIndexing
00976         //static_cast<util::PropertyMap::base_type*>(this)->clear(); we can keep the common properties - they will be merged with thier own copies from the chunks on the next reIndex
00977         splicer splice( dim, image_size.product(), *this );
00978         BOOST_FOREACH( boost::shared_ptr<Chunk> &ref, buffer ) {
00979             BOOST_FOREACH( const util::PropertyMap::KeyType & need, needed ) { //get back properties needed for the
00980                 if( !ref->hasProperty( need ) && this->hasProperty( need ) ) {
00981                     LOG( Debug, info ) << "Copying " << need << "=" << this->propertyValue( need ) << " from the image to the chunk for splicing";
00982                     ref->propertyValue( need ) = this->propertyValue( need );
00983                 }
00984             }
00985             splice( *ref );
00986         }
00987         reIndex();
00988         return lookup.size();
00989     }
00990 
00991     size_t Image::foreachChunk( ChunkOp & op, bool copyMetaData ) {
00992         size_t err = 0;
00993         if(checkMakeClean()){
00994             util::vector4<size_t> imgSize = getSizeAsVector();
00995             util::vector4<size_t> chunkSize = getChunk( 0, 0, 0, 0 ).getSizeAsVector();
00996             util::vector4<size_t> pos;
00997 
00998             for( pos[timeDim] = 0; pos[timeDim] < imgSize[timeDim]; pos[timeDim] += chunkSize[timeDim] ) {
00999                 for( pos[sliceDim] = 0; pos[sliceDim] < imgSize[sliceDim]; pos[sliceDim] += chunkSize[sliceDim] ) {
01000                     for( pos[columnDim] = 0; pos[columnDim] < imgSize[columnDim]; pos[columnDim] += chunkSize[columnDim] ) {
01001                         for( pos[rowDim] = 0; pos[rowDim] < imgSize[rowDim]; pos[rowDim] += chunkSize[rowDim] ) {
01002                             Chunk ch = getChunk( pos[rowDim], pos[columnDim], pos[sliceDim], pos[timeDim], copyMetaData );
01003 
01004                             if( op( ch, pos ) == false )
01005                                 err++;
01006                         }
01007                     }
01008                 }
01009             }
01010         }
01011         return err;
01012     }
01013 
01014     size_t Image::getNrOfColumns() const {
01015         return getDimSize( data::rowDim );
01016     }
01017 
01018     size_t Image::getNrOfRows() const {
01019         return getDimSize( data::columnDim );
01020     }
01021     size_t Image::getNrOfSlices() const {
01022         return getDimSize( data::sliceDim );
01023     }
01024     size_t Image::getNrOfTimesteps() const {
01025         return getDimSize( data::timeDim );
01026     }
01027 
01028     util::fvector3 Image::getFoV() const {
01029         util::fvector4 voxelGap;
01030 
01031         if ( hasProperty( "voxelGap" ) ) {
01032             voxelGap = getPropertyAs<util::fvector4>( "voxelGap" );
01033 
01034             for ( size_t i = 0; i < 3; i++ )
01035                 if ( voxelGap[i] == -std::numeric_limits<float>::infinity() ) {
01036                     LOG( Runtime, info ) << "Ignoring unknown voxel gap in direction " << i;
01037                     voxelGap[i] = 0;
01038                 }
01039         }
01040 
01041         const util::fvector4 ret = _internal::NDimensional<4>::getFoV( getPropertyAs<util::fvector4>( "voxelSize" ), voxelGap );
01042 
01043         LOG_IF( ret[timeDim], Runtime, warning ) << "Ignoring fourth dim extend of " << ret[timeDim] << " in Image";
01044 
01045         return util::fvector3( ret[0], ret[1], ret[2] );
01046     }
01047 
01048     Image::iterator Image::begin() {
01049         if( checkMakeClean() ) {
01050             std::vector<Chunk *> vec( lookup.size() );
01051 
01052             for( size_t i = 0; i < lookup.size(); i++ )
01053                 vec[i] = lookup[i].get();
01054 
01055             return iterator( vec );
01056         } else {
01057             LOG( Debug, error )  << "Image is not clean. Returning empty iterator ...";
01058             return iterator();
01059         }
01060     }
01061     Image::iterator Image::end() {return begin() + getVolume();}
01062     Image::const_iterator Image::begin()const {
01063         if( isClean() ) {
01064             std::vector<const Chunk *> vec( lookup.size() );
01065 
01066             for( size_t i = 0; i < lookup.size(); i++ )
01067                 vec[i] = lookup[i].get();
01068 
01069             return const_iterator( vec );
01070         } else {
01071             LOG( Debug, error )  << "Image is not clean. Returning empty iterator ...";
01072             return const_iterator();
01073         }
01074     }
01075     Image::const_iterator Image::end()const {return begin() + getVolume();}
01076 
01077     const util::ValueReference Image::getVoxelValue ( size_t nrOfColumns, size_t nrOfRows, size_t nrOfSlices, size_t nrOfTimesteps ) const {
01078         const size_t idx[] = {nrOfColumns, nrOfRows, nrOfSlices, nrOfTimesteps};
01079         LOG_IF( !isInRange( idx ), Debug, isis::error )
01080                 << "Index " << util::vector4<size_t>( idx ) << " is out of range (" << getSizeAsString() << ")";
01081         return begin()[getLinearIndex( idx )];
01082     }
01083     void Image::setVoxelValue ( const util::ValueReference & val, size_t nrOfColumns, size_t nrOfRows, size_t nrOfSlices, size_t nrOfTimesteps ) {
01084         const size_t idx[] = {nrOfColumns, nrOfRows, nrOfSlices, nrOfTimesteps};
01085         LOG_IF( !isInRange( idx ), Debug, isis::error )
01086                 << "Index " << util::vector4<size_t>( idx ) << " is out of range (" << getSizeAsString() << ")";
01087         begin()[getLinearIndex( idx )] = val;
01088     }
01089 
01090     std::string Image::identify ( bool withpath )const {
01091         return
01092             "\"S"
01093             + getPropertyAs<std::string>( "sequenceNumber" )
01094             + ( hasProperty( "sequenceDescription" ) ?
01095                 ( "_" + getPropertyAs<std::string>( "sequenceDescription" ) ) :
01096                 ""
01097               ) + "\""
01098             + ( withpath ?
01099                 ( std::string( " from " ) + getCommonSource( *this ).file_string() ) :
01100                 "" )
01101             + ( hasProperty( "sequenceStart" ) ?
01102                 ( " taken at " + getPropertyAs<std::string>( "sequenceStart" ) ) :
01103                 ""
01104               );
01105     }
01106 
01107 
01108 } // END namespace data
01109 } // END namespace isis