Added support for recording the RescaleIntecept and RescaleSlope from the dicome files and passing these values onto osgVolume::ImageLayer

This commit is contained in:
Robert Osfield 2009-09-01 10:48:32 +00:00
parent ea43bc7d52
commit 43e3089417
4 changed files with 460 additions and 318 deletions

View File

@ -700,7 +700,7 @@ osg::Image* doColourSpaceConversion(ColourSpaceOperation op, osg::Image* image,
osg::Image* newImage = new osg::Image;
newImage->allocateImage(image->s(), image->t(), image->r(), GL_LUMINANCE, image->getDataType());
osg::copyImage(image, 0, 0, 0, image->s(), image->t(), image->r(),
newImage, 0, 0, 0, false);
newImage, 0, 0, 0, false);
return newImage;
}
default:
@ -1030,7 +1030,7 @@ int main( int argc, char **argv )
double sequenceLength = 10.0;
while(arguments.read("--sequence-duration", sequenceLength) ||
arguments.read("--sd", sequenceLength)) {}
arguments.read("--sd", sequenceLength)) {}
typedef std::list< osg::ref_ptr<osg::Image> > Images;
Images images;
@ -1040,7 +1040,7 @@ int main( int argc, char **argv )
while (arguments.read("--vh", vh_filename))
{
std::string raw_filename, transfer_filename;
int xdim(0), ydim(0), zdim(0);
int xdim(0), ydim(0), zdim(0);
osgDB::ifstream header(vh_filename.c_str());
if (header)
@ -1161,7 +1161,7 @@ int main( int argc, char **argv )
if (fileType == osgDB::DIRECTORY)
{
osg::Image *image = osgDB::readImageFile(filename+".dicom");
osg::Image *image = osgDB::readImageFile(filename+".dicom");
if(image)
{
images.push_back(image);
@ -1206,7 +1206,8 @@ int main( int argc, char **argv )
}
osg::ref_ptr<osg::RefMatrix> matrix = dynamic_cast<osg::RefMatrix*>(images.front()->getUserData());
osg::ref_ptr<osgVolume::ImageDetails> details = dynamic_cast<osgVolume::ImageDetails*>(images.front()->getUserData());
osg::ref_ptr<osg::RefMatrix> matrix = details ? details->getMatrix() : dynamic_cast<osg::RefMatrix*>(images.front()->getUserData());
if (!matrix)
{
@ -1261,7 +1262,7 @@ int main( int argc, char **argv )
maxComponent = osg::maximum(maxComponent,maxValue[2]);
maxComponent = osg::maximum(maxComponent,maxValue[3]);
#if 0
switch(rescaleOperation)
{
case(NO_RESCALE):
@ -1297,7 +1298,7 @@ int main( int argc, char **argv )
break;
}
};
#endif
}
@ -1348,7 +1349,30 @@ int main( int argc, char **argv )
osg::ref_ptr<osgVolume::VolumeTile> tile = new osgVolume::VolumeTile;
volume->addChild(tile.get());
osg::ref_ptr<osgVolume::Layer> layer = new osgVolume::ImageLayer(image_3d.get());
osg::ref_ptr<osgVolume::ImageLayer> layer = new osgVolume::ImageLayer(image_3d.get());
if (details)
{
layer->setRescaleIntercept(details->getRescaleIntercept());
layer->setRescaleSlope(details->getRescaleSlope());
}
switch(rescaleOperation)
{
case(NO_RESCALE):
break;
case(RESCALE_TO_ZERO_TO_ONE_RANGE):
{
layer->rescaleToZeroToOneRange();
break;
}
case(SHIFT_MIN_TO_ZERO):
{
layer->translateMinToZero();
break;
}
};
layer->setLocator(new osgVolume::Locator(*matrix));
tile->setLocator(new osgVolume::Locator(*matrix));

View File

@ -22,6 +22,37 @@
namespace osgVolume {
/** Data strucutre for passing details about the loading imagery on to osgVolume for use when setting up dimensions etc.*/
class OSGVOLUME_EXPORT ImageDetails : public osg::Object
{
public:
ImageDetails();
/** Copy constructor using CopyOp to manage deep vs shallow copy.*/
ImageDetails(const ImageDetails&,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
META_Object(osgVolume, ImageDetails);
void setRescaleIntercept(double intercept) { _rescaleIntercept = intercept; }
double getRescaleIntercept() const { return _rescaleIntercept; }
void setRescaleSlope(double slope) { _rescaleSlope = slope; }
double getRescaleSlope() const { return _rescaleSlope; }
void setMatrix(osg::RefMatrix* matrix) { _matrix = matrix; }
osg::RefMatrix* getMatrix() { return _matrix.get(); }
const osg::RefMatrix* getMatrix() const { return _matrix.get(); }
protected:
double _rescaleIntercept;
double _rescaleSlope;
osg::ref_ptr<osg::RefMatrix> _matrix;
};
/** Base class for representing a single layer of volume data.*/
class OSGVOLUME_EXPORT Layer : public osg::Object
{
public:
@ -131,6 +162,14 @@ class OSGVOLUME_EXPORT ImageLayer : public Layer
/** Return const image associated with layer. */
virtual const osg::Image* getImage() const { return _image.get(); }
void setRescaleIntercept(double intercept) { _rescaleIntercept = intercept; }
double getRescaleIntercept() const { return _rescaleIntercept; }
void setRescaleSlope(double slope) { _rescaleSlope = slope; }
double setRescaleSlope() const { return _rescaleSlope; }
/** Compute the min and max pixel colors.*/
bool computeMinMax(osg::Vec4& min, osg::Vec4& max);
@ -155,6 +194,8 @@ class OSGVOLUME_EXPORT ImageLayer : public Layer
virtual ~ImageLayer() {}
double _rescaleIntercept;
double _rescaleSlope;
osg::ref_ptr<osg::Image> _image;
};

View File

@ -26,6 +26,7 @@
#include <dcmtk/config/osconfig.h>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmdata/dcuid.h>
#include <dcmtk/dcmimgle/dcmimage.h>
#endif
@ -206,7 +207,14 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
tile->setLayer(layer.get());
// get matrix providing size of texels (in mm)
osg::RefMatrix* matrix = dynamic_cast<osg::RefMatrix*>(result.getImage()->getUserData());
osgVolume::ImageDetails* details = dynamic_cast<osgVolume::ImageDetails*>(result.getImage()->getUserData());
osg::RefMatrix* matrix = details ? details->getMatrix() : 0;
if (details)
{
layer->setRescaleIntercept(details->getRescaleIntercept());
layer->setRescaleSlope(details->getRescaleSlope());
}
if (matrix)
{
@ -298,7 +306,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
++itr)
{
osg::Image* image = itr->get();
osg::RefMatrix* matrix = dynamic_cast<osg::RefMatrix*>(image->getUserData());
osgVolume::ImageDetails* details = dynamic_cast<osgVolume::ImageDetails*>(result.getImage()->getUserData());
osg::RefMatrix* matrix = details ? details->getMatrix() : 0;
if (matrix)
{
osg::Vec3 p0 = osg::Vec3(0.0, 0.0, 0.0) * (*matrix);
@ -340,18 +349,19 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
{
osg::Image* image = itr->second.get();
osg::copyImage(image, 0,0,0, image->s(), image->t(), image->r(),
image3D.get(), 0, 0, r,
false);
image3D.get(), 0, 0, r,
false);
r += image->r();
}
osg::Image* firstImage = dim.begin()->second.get();
osg::RefMatrix* matrix = dynamic_cast<osg::RefMatrix*>(firstImage->getUserData());
osgVolume::ImageDetails* details = dynamic_cast<osgVolume::ImageDetails*>(result.getImage()->getUserData());
osg::RefMatrix* matrix = details ? details->getMatrix() : 0;
if (matrix)
{
image3D->setUserData(
new osg::RefMatrix(osg::Matrix::scale(1.0,1.0,totalThickness) * (*matrix))
);
osgVolume::ImageDetails* details3D = new osgVolume::ImageDetails(*details);
details3D->getMatrix()->preMult(osg::Matrix::scale(1.0,1.0,totalThickness));
image3D->setUserData(details3D);
}
return image3D.get();
@ -422,7 +432,10 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
++it;
}
image->setUserData(matrix);
osgVolume::ImageDetails* details = new osgVolume::ImageDetails;
details->setMatrix(matrix);
image->setUserData(details);
matrix->preMult(osg::Matrix::scale(double(image->s()), double(image->t()), double(image->r())));
@ -433,8 +446,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
#ifdef USE_DCMTK
void convertPixelTypes(const DiPixel* pixelData,
EP_Representation& pixelRep, int& numPlanes,
GLenum& dataType, GLenum& pixelFormat, unsigned int& pixelSize) const
EP_Representation& pixelRep, int& numPlanes,
GLenum& dataType, GLenum& pixelFormat, unsigned int& pixelSize) const
{
dataType = GL_UNSIGNED_BYTE;
pixelRep = pixelData->getRepresentation();
@ -534,8 +547,11 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
return ReadResult::FILE_NOT_FOUND;
}
osg::ref_ptr<osg::RefMatrix> matrix = new osg::RefMatrix;
osg::ref_ptr<osgVolume::ImageDetails> details = new osgVolume::ImageDetails;
details->setMatrix(new osg::RefMatrix);
osg::ref_ptr<osg::Image> image;
unsigned int imageNum = 0;
EP_Representation pixelRep = EPR_Uint8;
int numPlanes = 0;
@ -570,6 +586,32 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
double imageOrientationPatient[6] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0 };
Uint16 numOfSlices = 1;
// code for reading the intercept and scale that is required to convert to Hounsfield units.
bool rescaling = false;
double rescaleIntercept = 0.0;
double rescaleSlope = 1.0;
const char *classUID = NULL;
if (fileformat.getDataset()->findAndGetString(DCM_SOPClassUID, classUID).good())
{
osg::notify(osg::NOTICE)<<" classUID = "<<classUID<<std::endl;
if (0 == strcmp(classUID, UID_CTImageStorage))
{
osg::notify(osg::NOTICE)<<" is a UID_CTImageStorage "<<std::endl;
}
}
rescaling = fileformat.getDataset()->findAndGetFloat64(DCM_RescaleIntercept, rescaleIntercept).good();
rescaling &= fileformat.getDataset()->findAndGetFloat64(DCM_RescaleSlope, rescaleSlope).good();
if (rescaling)
{
fileInfo.rescaleIntercept = rescaleIntercept;
fileInfo.rescaleSlope = rescaleSlope;
osg::notify(osg::NOTICE)<<" rescaleIntercept = "<<rescaleIntercept<<std::endl;
osg::notify(osg::NOTICE)<<" rescaleSlope = "<<rescaleSlope<<std::endl;
}
double value = 0.0;
if (fileformat.getDataset()->findAndGetFloat64(DCM_PixelSpacing, value,0).good())
{
@ -758,15 +800,15 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
// create the new image
convertPixelTypes(pixelData,
curr_pixelRep, curr_numPlanes,
curr_dataType, curr_pixelFormat, curr_pixelSize);
curr_pixelRep, curr_numPlanes,
curr_dataType, curr_pixelFormat, curr_pixelSize);
imageAdapter->setImage(dcmImage->getWidth(), dcmImage->getHeight(), dcmImage->getFrameCount(),
curr_pixelFormat,
curr_pixelFormat,
curr_dataType,
(unsigned char*)(pixelData->getData()),
osg::Image::NO_DELETE);
curr_pixelFormat,
curr_pixelFormat,
curr_dataType,
(unsigned char*)(pixelData->getData()),
osg::Image::NO_DELETE);
if (!image)
{
pixelRep = curr_pixelRep;
@ -775,6 +817,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
pixelFormat = curr_pixelFormat;
pixelSize = curr_pixelSize;
osg::RefMatrix* matrix = details->getMatrix();
(*matrix)(0,0) = fileInfo.matrix(0,0);
(*matrix)(1,0) = fileInfo.matrix(1,0);
(*matrix)(2,0) = fileInfo.matrix(2,0);
@ -785,13 +829,14 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
(*matrix)(1,2) = fileInfo.matrix(1,2) * averageThickness;
(*matrix)(2,2) = fileInfo.matrix(2,2) * averageThickness;
details->setRescaleIntercept(fileInfo.rescaleIntercept);
details->setRescaleSlope(fileInfo.rescaleSlope);
image = new osg::Image;
image->setUserData(matrix.get());
image->setUserData(details.get());
image->setFileName(fileName.c_str());
image->allocateImage(dcmImage->getWidth(), dcmImage->getHeight(), totalNumSlices,
pixelFormat, dataType);
pixelFormat, dataType);
matrix->preMult(osg::Matrix::scale(double(image->s()), double(image->t()), double(image->r())));
@ -799,7 +844,7 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
info()<<"Image dimensions = "<<image->s()<<", "<<image->t()<<", "<<image->r()<<" pixelFormat=0x"<<std::hex<<pixelFormat<<" dataType=0x"<<std::hex<<dataType<<std::dec<<std::endl;
}
else if (pixelData->getPlanes()>numPlanes ||
pixelData->getRepresentation()>pixelRep)
pixelData->getRepresentation()>pixelRep)
{
info()<<"Need to reallocated "<<image->s()<<", "<<image->t()<<", "<<image->r()<<std::endl;
@ -808,24 +853,24 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
// create the new image
convertPixelTypes(pixelData,
pixelRep, numPlanes,
dataType, pixelFormat, pixelSize);
pixelRep, numPlanes,
dataType, pixelFormat, pixelSize);
image = new osg::Image;
image->setUserData(previous_image->getUserData());
image->setFileName(fileName.c_str());
image->allocateImage(dcmImage->getWidth(), dcmImage->getHeight(), totalNumSlices,
pixelFormat, dataType);
pixelFormat, dataType);
osg::copyImage(previous_image.get(), 0,0,0, previous_image->s(), previous_image->t(), imageNum,
image.get(), 0, 0, 0,
false);
image.get(), 0, 0, 0,
false);
}
osg::copyImage(imageAdapter.get(), 0,0,0, imageAdapter->s(), imageAdapter->t(), imageAdapter->r(),
image.get(), 0, 0, imageNum,
false);
image.get(), 0, 0, imageNum,
false);
imageNum += dcmImage->getFrameCount();
}
@ -841,7 +886,7 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
return ReadResult::ERROR_IN_READING_FILE;
}
info()<<"Spacing = "<<*matrix<<std::endl;
info()<<"Spacing = "<<*(details->getMatrix())<<std::endl;
return image.get();
}
@ -850,6 +895,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
struct FileInfo
{
FileInfo():
rescaleIntercept(0.0),
rescaleSlope(1.0),
numX(0),
numY(0),
numSlices(1),
@ -859,6 +906,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
FileInfo(const FileInfo& rhs):
filename(rhs.filename),
matrix(rhs.matrix),
rescaleIntercept(rhs.rescaleIntercept),
rescaleSlope(rhs.rescaleSlope),
numX(rhs.numX),
numY(rhs.numY),
numSlices(rhs.numSlices),
@ -871,6 +920,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
filename = rhs.filename;
matrix = rhs.matrix;
rescaleIntercept = rhs.rescaleIntercept;
rescaleSlope = rhs.rescaleSlope;
numX = rhs.numX;
numY = rhs.numY;
sliceThickness = rhs.sliceThickness;
@ -882,6 +933,8 @@ class ReaderWriterDICOM : public osgDB::ReaderWriter
std::string filename;
osg::Matrixd matrix;
double rescaleIntercept;
double rescaleSlope;
unsigned int numX;
unsigned int numY;
unsigned int numSlices;

View File

@ -1,14 +1,14 @@
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include <osgVolume/Layer>
@ -21,6 +21,19 @@
using namespace osgVolume;
ImageDetails::ImageDetails():
_rescaleIntercept(0.0),
_rescaleSlope(1.0)
{
}
ImageDetails::ImageDetails(const ImageDetails& rhs,const osg::CopyOp& copyop):
_rescaleIntercept(rhs._rescaleIntercept),
_rescaleSlope(rhs._rescaleSlope),
_matrix(rhs._matrix)
{
}
Layer::Layer():
_minFilter(osg::Texture::LINEAR),
_magFilter(osg::Texture::LINEAR)
@ -82,12 +95,16 @@ void Layer::addProperty(Property* property)
// ImageLayer
//
ImageLayer::ImageLayer(osg::Image* image):
_rescaleIntercept(0.0),
_rescaleSlope(1.0),
_image(image)
{
}
ImageLayer::ImageLayer(const ImageLayer& imageLayer,const osg::CopyOp& copyop):
Layer(imageLayer, copyop),
_rescaleIntercept(imageLayer._rescaleIntercept),
_rescaleSlope(imageLayer._rescaleSlope),
_image(imageLayer._image)
{
}
@ -129,6 +146,10 @@ void ImageLayer::offsetAndScaleImage(const osg::Vec4& offset, const osg::Vec4& s
void ImageLayer::rescaleToZeroToOneRange()
{
osg::notify(osg::NOTICE)<<"ImageLayer::rescaleToZeroToOneRange()"<<std::endl;
osg::notify(osg::NOTICE)<<" _rescaleIntercept "<<_rescaleIntercept<<std::endl;
osg::notify(osg::NOTICE)<<" _rescaleSlope "<<_rescaleSlope<<std::endl;
osg::Vec4 minValue, maxValue;
if (computeMinMax(minValue, maxValue))
{
@ -145,6 +166,9 @@ void ImageLayer::rescaleToZeroToOneRange()
float scale = 0.99f/(maxComponent-minComponent);
float offset = -minComponent * scale;
osg::notify(osg::NOTICE)<<" scale "<<scale<<std::endl;
osg::notify(osg::NOTICE)<<" offset "<<offset<<std::endl;
offsetAndScaleImage(osg::Vec4(offset, offset, offset, offset),
osg::Vec4(scale, scale, scale, scale));
}
@ -284,8 +308,8 @@ osg::Image* osgVolume::createNormalMapTexture(osg::Image* image_3d)
{
osg::Vec3 grad((float)(*left)-(float)(*right),
(float)(*below)-(float)(*above),
(float)(*out) -(float)(*in));
(float)(*below)-(float)(*above),
(float)(*out) -(float)(*in));
grad.normalize();
@ -331,8 +355,8 @@ osg::Image* osgVolume::createNormalMapTexture(osg::Image* image_3d)
{
osg::Vec3 grad((float)(*left)-(float)(*right),
(float)(*below)-(float)(*above),
(float)(*out) -(float)(*in));
(float)(*below)-(float)(*above),
(float)(*out) -(float)(*in));
grad.normalize();
@ -381,8 +405,8 @@ osg::Image* osgVolume::createNormalMapTexture(osg::Image* image_3d)
{
osg::Vec3 grad((float)(*left)-(float)(*right),
(float)(*below)-(float)(*above),
(float)(*out) -(float)(*in));
(float)(*below)-(float)(*above),
(float)(*out) -(float)(*in));
grad.normalize();