cmtkVolumeFromSlices.cxx

Go to the documentation of this file.
00001 /*
00002 //
00003 //  Copyright 1997-2009 Torsten Rohlfing
00004 //
00005 //  Copyright 2004-2010 SRI International
00006 //
00007 //  This file is part of the Computational Morphometry Toolkit.
00008 //
00009 //  http://www.nitrc.org/projects/cmtk/
00010 //
00011 //  The Computational Morphometry Toolkit is free software: you can
00012 //  redistribute it and/or modify it under the terms of the GNU General Public
00013 //  License as published by the Free Software Foundation, either version 3 of
00014 //  the License, or (at your option) any later version.
00015 //
00016 //  The Computational Morphometry Toolkit is distributed in the hope that it
00017 //  will be useful, but WITHOUT ANY WARRANTY; without even the implied
00018 //  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019 //  GNU General Public License for more details.
00020 //
00021 //  You should have received a copy of the GNU General Public License along
00022 //  with the Computational Morphometry Toolkit.  If not, see
00023 //  <http://www.gnu.org/licenses/>.
00024 //
00025 //  $Revision: 2570 $
00026 //
00027 //  $LastChangedDate: 2010-11-30 14:36:45 -0800 (Tue, 30 Nov 2010) $
00028 //
00029 //  $LastChangedBy: torsten_at_home $
00030 //
00031 */
00032 
00033 #include "cmtkVolumeFromSlices.h"
00034 
00035 #include <System/cmtkProgress.h>
00036 
00037 #include <Base/cmtkVolume.h>
00038 #include <Base/cmtkUniformVolume.h>
00039 #include <Base/cmtkAffineXform.h>
00040 #include <Base/cmtkMathUtil.h>
00041 #include <Base/cmtkAnatomicalOrientation.h>
00042 
00043 #include <math.h>
00044 
00045 namespace
00046 cmtk
00047 {
00048 
00051 
00052 void 
00053 VolumeFromSlices::InitSequence
00054 ( const ScalarImage* image, const unsigned int numberOfSlices )
00055 { 
00056   Padding = false;
00057 
00058   Spacing[0] = image->GetPixelSize( AXIS_X );
00059   Spacing[1] = image->GetPixelSize( AXIS_Y );
00060 
00061   ImagePosition = image->GetImageOrigin();
00062 
00063   Dims[0] = image->GetDims()[AXIS_X];
00064   Dims[1] = image->GetDims()[AXIS_Y];
00065   Dims[2] = numberOfSlices;
00066 
00067   BytesPerPixel = image->GetPixelData()->GetItemSize();
00068   DataType = image->GetPixelData()->GetType();
00069 
00070   DataSize = Dims[0] * Dims[1] * Dims[2];
00071 
00072   VolumeDataArray = TypedArray::Create( image->GetPixelData()->GetType(), DataSize );
00073   
00074   // Allocate array for axis sample points
00075   for ( unsigned int idx = 0; idx<3; ++idx )
00076     Points[idx] = Memory::AllocateArray<Types::Coordinate>( Dims[idx] );
00077   
00078   // Set sample points for uniform original x- and y-axis
00079   for ( unsigned int dim=0; dim<2; ++dim ) 
00080     {
00081     for ( int idx=0; idx < Dims[dim]; ++idx ) 
00082       {
00083       Points[dim][idx] = idx * Spacing[dim];
00084       }
00085     // Set size in axis direction.
00086     Size[dim] = (Dims[dim]-1) * Spacing[dim];
00087     }
00088 }
00089 
00090 char* 
00091 VolumeFromSlices::AllocDataArray
00092 ( const int bytesPerPixel, const int dataSize ) const 
00093 {
00094   return Memory::AllocateArray<char>( bytesPerPixel * dataSize );
00095 }
00096 
00097 TypedArray::SmartPtr
00098 VolumeFromSlices::EncapDataArray ( const ScalarDataType dtype, void *const data, const int data_size ) 
00099   const
00100 {
00101   return TypedArray::Create( dtype, data, data_size, true /*freeArray*/, Padding, &PaddingValue );
00102 }
00103 
00104 const char* 
00105 VolumeFromSlices::FillPlane 
00106 ( unsigned int& plane, const ScalarImage* image )
00107 {
00108   char* rawDataPtr = static_cast<char*>( VolumeDataArray->GetDataPtr() );
00109 
00110   const size_t bytesPerBlock = BytesPerPixel * Dims[0] * Dims[1];
00111   for ( int planeIdx = 0; planeIdx < image->GetNumberOfFrames(); ++planeIdx, ++plane ) 
00112     {
00113     const char *check = this->CheckImage( plane, image, planeIdx );
00114     if ( check ) return check;
00115     
00116     memcpy( rawDataPtr + bytesPerBlock * plane, image->GetPixelData()->GetDataPtr(), bytesPerBlock );
00117     
00118     // set world coordinate of the plane just read
00119     Types::Coordinate slicePosition = (ImagePosition - FirstImagePosition).RootSumOfSquares();
00120     slicePosition = 1e-6 * ( MathUtil::Round( 1e+6 * slicePosition) );
00121     Points[2][plane] = slicePosition;
00122     }
00123   
00124   return NULL;
00125 }
00126 
00127 UniformVolume::SmartPtr
00128 VolumeFromSlices::FinishVolume ( Types::Coordinate& sliceOffset, int& sliceDirection )
00129 {
00130   Types::Coordinate *next_point = Points[2];
00131 
00132   sliceOffset = next_point[0];
00133   sliceDirection = MathUtil::Sign(next_point[1]-sliceOffset);
00134   
00135   Types::Coordinate previous_plane = sliceOffset;
00136   
00137   // normalize z-coordinates so that they start with zero and increase with
00138   // growing z-index.
00139   *next_point = 0;
00140   int idx;
00141   for ( idx=1, ++next_point; idx < Dims[2]; ++idx, ++next_point ) 
00142     {
00143     Types::Coordinate next_plane = *next_point;
00144     (*next_point) = *(next_point-1)+fabs(next_plane-previous_plane);
00145     previous_plane = next_plane;
00146     }
00147   
00148   Size[2] = *(next_point-1);
00149   
00150   // Encapsulate raw volume data.
00151   
00152   if ( !VolumeDataArray )
00153     VolumeDataArray = TypedArray::SmartPtr( this->EncapDataArray( SelectDataTypeInteger( BytesPerPixel, SignBit ), RawData, DataSize ) );
00154   
00155   const Types::Coordinate* aux[] = { Points[0], Points[1], Points[2] };
00156   UniformVolume::SmartPtr Result = this->ConstructVolume( Dims, Size, aux, VolumeDataArray );
00157 
00158   // clear reference, since now linked by volume.
00159   VolumeDataArray = TypedArray::SmartPtr::Null; 
00160 
00161   for ( idx = 0; idx<3; ++idx )
00162     Memory::DeleteArray( Points[idx] );
00163 
00164   Result->m_MetaInformation[META_SPACE] = Result->m_MetaInformation[META_SPACE_ORIGINAL] = "LPS";
00165 
00166   // actual image directions
00167   const Types::Coordinate spacing[3] = { (Size[0] / (Dims[0]-1)), (Size[1] / (Dims[1]-1)), (Size[2] / (Dims[2]-1)) };
00168 
00169   this->ImageOrientation[0] *= spacing[0] / this->ImageOrientation[0].RootSumOfSquares();
00170   this->ImageOrientation[1] *= spacing[1] / this->ImageOrientation[1].RootSumOfSquares();
00171   this->IncrementVector *= spacing[2] / this->IncrementVector.RootSumOfSquares();
00172   
00173   const Types::Coordinate directions[3][3] = 
00174     {
00175       { this->ImageOrientation[0][0], this->ImageOrientation[0][1], this->ImageOrientation[0][2] },
00176       { this->ImageOrientation[1][0], this->ImageOrientation[1][1], this->ImageOrientation[1][2] },
00177       { this->IncrementVector[0], this->IncrementVector[1], this->IncrementVector[2] }
00178     };
00179   
00180   const Matrix3x3<Types::Coordinate> m3( directions );
00181   Matrix4x4<Types::Coordinate> m4( m3 );
00182   for ( int i = 0; i < 3; ++i )
00183     m4[3][i] = this->FirstImagePosition[i];
00184 
00185   Result->m_IndexToPhysicalMatrix = m4;
00186   const std::string orientationString0 = Result->GetOrientationFromDirections();
00187   Result->ChangeCoordinateSpace( AnatomicalOrientation::ORIENTATION_STANDARD );
00188 
00189   const std::string orientationString = Result->GetOrientationFromDirections();
00190   Result->m_MetaInformation[META_SPACE_UNITS_STRING] = "mm"; // seems to be implied in DICOM
00191   Result->m_MetaInformation[META_IMAGE_ORIENTATION] = orientationString;
00192   Result->m_MetaInformation[META_IMAGE_ORIENTATION_ORIGINAL] = orientationString;
00193 
00194   return Result;
00195 }
00196 
00197 const char* 
00198 VolumeFromSlices::CheckImage
00199 ( const int plane, const ScalarImage* image, const unsigned int frame )
00200 {
00201   if ( ( this->Dims[0] != image->GetDims()[AXIS_X] ) || ( this->Dims[1] != image->GetDims()[AXIS_Y] ) )
00202     return "Image size mismatch";
00203   
00204   if ( ( fabs( image->GetPixelSize( AXIS_X ) - Spacing[0] ) > CMTK_MAX_CALIB_ERROR ) ||
00205        ( fabs( image->GetPixelSize( AXIS_Y ) - Spacing[1] ) > CMTK_MAX_CALIB_ERROR ) )
00206     return "Calibration mismatch";
00207   
00208   // not too many things can go wrong for the very first slice.
00209   if ( plane == 0 ) 
00210     {
00211     FirstImagePosition = ImagePosition = image->GetImageOrigin( frame );
00212     ImageOrientation[0] = image->GetImageDirectionX();
00213     ImageOrientation[1] = image->GetImageDirectionY();
00214     return NULL;
00215     }
00216 
00217   // check whether this slice is parallel to the previous one
00218   for ( unsigned int dim = 0; dim<3; ++dim ) 
00219     {
00220     if ( ( fabs( ImageOrientation[0][dim] - image->GetImageDirectionX()[dim] ) > CMTK_MAX_CALIB_ERROR ) ||
00221          ( fabs( ImageOrientation[1][dim] - image->GetImageDirectionY()[dim] ) > CMTK_MAX_CALIB_ERROR ) )
00222       return "Non-parallel image planes";
00223     }
00224   
00225   // Second++ slice: Compute slice-to-slice vector
00226   ScalarImage::SpaceVectorType imageToImage = image->GetImageOrigin( frame ) - ImagePosition;
00227   
00228   if ( imageToImage.MaxAbsValue() < CMTK_MAX_LOCALIZE_ERROR )
00229     {
00230     StdErr.printf( "Two slices at position (%f,%f,%f)\n", (float)ImagePosition[0], (float)ImagePosition[1], (float)ImagePosition[2] );
00231     return "Encountered two slices in identical location.";
00232     }
00233   else
00234     imageToImage /= imageToImage.MaxAbsValue();
00235   
00236   // Check whether slice-to-slice direction is orthogonal to image
00237   // axes.
00238   const Types::Coordinate scalarX = fabs( imageToImage * ImageOrientation[0] );
00239   const Types::Coordinate scalarY = fabs( imageToImage * ImageOrientation[1] );
00240   if ( (scalarX > CMTK_MAX_ANGLE_ERROR) || (scalarY > CMTK_MAX_ANGLE_ERROR) )
00241     {
00242     fprintf( stderr, "errX = %f, errY = %f, thresh = %f\n", scalarX, scalarY, CMTK_MAX_ANGLE_ERROR );
00243     return "Data grid must be orthogonal.";
00244     }
00245   
00246   // if this is the second slice, save increment vector for further tests.
00247   if ( plane == 1 )
00248     IncrementVector = imageToImage;
00249   // otherwise, perform these tests
00250   else 
00251     {
00252     // Are we still going in the same direction?
00253     if ( (imageToImage - IncrementVector).MaxAbsValue() > CMTK_MAX_LOCALIZE_ERROR )
00254       {
00255       // Nope, but why? Let's give user some more hints
00256       if ( ( (imageToImage * IncrementVector) > 0 ) )
00257         // Basically same direction, so FOV has changed
00258         return "Field-of-view mismatch";
00259       else
00260         // Completely different direction: We're going backwards
00261         return "Encountered altering slice direction.";
00262       }
00263     }
00264   
00265   // Finally, save essential information about current image.
00266   ImagePosition = image->GetImageOrigin( frame );
00267   
00268   return NULL;
00269 }
00270 
00271 UniformVolume::SmartPtr 
00272 VolumeFromSlices::ConstructVolume
00273 ( const DataGrid::IndexType& dims, const UniformVolume::CoordinateVectorType& size, const Types::Coordinate *points[3], TypedArray::SmartPtr& data ) const
00274 {
00275   bool isUniform = true;
00276   Types::Coordinate error = 0;
00277   for ( unsigned int dim=0; (dim<3) && isUniform; ++dim ) 
00278     {
00279     Types::Coordinate delta = points[dim][1] - points[dim][0];
00280     for ( int idx=2; (idx<dims[dim]) && isUniform; ++idx ) 
00281       {
00282       if ( fabs( delta - (points[dim][idx] - points[dim][idx-1]) ) > (0.0001 * delta ) )
00283         isUniform = false;
00284       error = fabs( delta - (points[dim][idx] - points[dim][idx-1]) );
00285       }
00286     }
00287   
00288   if ( !isUniform )
00289     {
00290     StdErr << "WARNING: not a uniform volume (error = " << error << ")\n";
00291     }
00292   return UniformVolume::SmartPtr( new UniformVolume( dims, size, data ) );
00293 }
00294 
00295 } // namespace cmtk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines