X-Git-Url: https://git.creatis.insa-lyon.fr/pubgit/?a=blobdiff_plain;f=vtk%2FvtkGdcmWriter.cxx;h=2b5764734e7bf721b050436b65e9f878b76bfac8;hb=fb1db702e2e94adb9c04387604dbe635726db898;hp=a7ca870e9768dc3693d979304b8f566ece420618;hpb=51e53b78f995e2dde4f75570a8bf3b0dac5209c2;p=gdcm.git diff --git a/vtk/vtkGdcmWriter.cxx b/vtk/vtkGdcmWriter.cxx index a7ca870e..2b576473 100644 --- a/vtk/vtkGdcmWriter.cxx +++ b/vtk/vtkGdcmWriter.cxx @@ -1,252 +1,456 @@ -// vtkGdcmWriter.cxx -//----------------------------------------------------------------------------- -// ////////////////////////////////////////////////////////////// -// WARNING TODO CLEANME -// Actual limitations of this code: -// -// /////// Redundant and unnecessary header parsing -// In it's current state this code actually parses three times the Dicom -// header of a file before the corresponding image gets loaded in the -// ad-hoc vtkData ! -// Here is the process: -// 1/ First loading happens in ExecuteInformation which in order to -// positionate the vtk extents calls CheckFileCoherence. The purpose -// of CheckFileCoherence is to make sure all the images in the future -// stack are "homogenous" (same size, same representation...). This -// can only be achieved by parsing all the Dicom headers... -// 2/ ExecuteData is then responsible for the next two loadings: -// 2a/ ExecuteData calls AllocateOutputData that in turn seems to -// (indirectely call) ExecuteInformation which ends up in a second -// header parsing -// This is fixed by adding a test at the beginning of ExecuteInformation -// on the modification of the object instance. If a modification have been -// made (method Modified() ), the MTime value is increased. The fileTime -// is compared to this new value to find a modification in the class -// parameters -// 2b/ the core of ExecuteData then needs gdcmFile (which in turns -// initialises gdcmHeader in the constructor) in order to access -// the data-image. -// -// Possible solution: -// maintain a list of gdcmFiles (created by say ExecuteInformation) created -// once and for all accross the life of vtkGdcmHeader (it would only load -// new gdcmFile if the user changes the list). ExecuteData would then use -// those gdcmFile and hence avoid calling the construtor: -// - advantage: the header of the files would only be parser once. -// - drawback: once execute information is called (i.e. on creation of -// a vtkGdcmHeader) the gdcmFile structure is loaded in memory. -// The average size of a gdcmHeader being of 100Ko, is one -// loads 10 stacks of images with say 200 images each, you -// end-up with a loss of 200Mo... -// -// /////// Never unallocated memory: -// ExecuteData allocates space for the pixel data [which will get pointed -// by the vtkPointData() through the call -// data->GetPointData()->GetScalars()->SetVoidArray(mem, StackNumPixels, 0);] -// This data is never "freed" neither in the destructor nor when the -// filename list is extended, ExecuteData is called a second (or third) -// time... -// ////////////////////////////////////////////////////////////// - -#include "gdcmHeader.h" +/*========================================================================= + + Program: gdcm + Module: $RCSfile: vtkGdcmWriter.cxx,v $ + Language: C++ + Date: $Date: 2007/12/13 15:16:19 $ + Version: $Revision: 1.36 $ + + Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de + l'Image). All rights reserved. See Doc/License.txt or + http://www.creatis.insa-lyon.fr/Public/Gdcm/License.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + #include "gdcmFile.h" +#include "gdcmFileHelper.h" #include "gdcmDebug.h" +#include "gdcmUtil.h" #include "vtkGdcmWriter.h" #include #include #include #include - -vtkCxxRevisionMacro(vtkGdcmWriter, "$Revision: 1.3 $"); -vtkStandardNewMacro(vtkGdcmWriter); - +#if (VTK_MAJOR_VERSION >= 5) +#include +#endif +#ifndef vtkFloatingPointType +#define vtkFloatingPointType float +#endif + +vtkCxxRevisionMacro(vtkGdcmWriter, "$Revision: 1.36 $") +vtkStandardNewMacro(vtkGdcmWriter) + +vtkCxxSetObjectMacro(vtkGdcmWriter,LookupTable,vtkLookupTable) +#if (VTK_MAJOR_VERSION >= 5) +vtkCxxSetObjectMacro(vtkGdcmWriter,MedicalImageProperties,vtkMedicalImageProperties) +#endif //----------------------------------------------------------------------------- // Constructor / Destructor vtkGdcmWriter::vtkGdcmWriter() { this->LookupTable = NULL; + this->MedicalImageProperties = NULL; + this->FileDimensionality = 3; + this->WriteType = VTK_GDCM_WRITE_TYPE_EXPLICIT_VR; + this->GdcmFile = 0; + this->ContentType = VTK_GDCM_WRITE_TYPE_USER_OWN_IMAGE; } vtkGdcmWriter::~vtkGdcmWriter() { + this->SetMedicalImageProperties(NULL); + this->SetLookupTable(NULL); } //----------------------------------------------------------------------------- // Print -void vtkGdcmWriter::PrintSelf(ostream& os, vtkIndent indent) +void vtkGdcmWriter::PrintSelf(ostream &os, vtkIndent indent) { - this->Superclass::PrintSelf(os,indent); + this->Superclass::PrintSelf(os, indent); + + os << indent << "Write type : " << this->GetWriteTypeAsString(); } //----------------------------------------------------------------------------- // Public +const char *vtkGdcmWriter::GetWriteTypeAsString() +{ + switch(WriteType) + { + case VTK_GDCM_WRITE_TYPE_EXPLICIT_VR : + return "Explicit VR"; + case VTK_GDCM_WRITE_TYPE_IMPLICIT_VR : + return "Implicit VR"; + case VTK_GDCM_WRITE_TYPE_ACR : + return "ACR"; + case VTK_GDCM_WRITE_TYPE_ACR_LIBIDO : + return "ACR Libido"; + default : + return "Unknow type"; + } +} //----------------------------------------------------------------------------- // Protected /** * Copy the image and reverse the Y axis */ -// The output datas must be deleted by the user of the method !!! -size_t ReverseData(vtkImageData *img,unsigned char **data) +// The output data must be deleted by the user of the method !!! +size_t ReverseData(vtkImageData *image,unsigned char **data) { - int *dim = img->GetDimensions(); - size_t lineSize = dim[0] * img->GetScalarSize() - * img->GetNumberOfScalarComponents(); +#if (VTK_MAJOR_VERSION >= 5) + vtkIdType inc[3]; +#else + int inc[3]; +#endif + int *extent = image->GetUpdateExtent(); + int dim[3] = {extent[1]-extent[0]+1, + extent[3]-extent[2]+1, + extent[5]-extent[4]+1}; + + size_t lineSize = dim[0] * image->GetScalarSize() + * image->GetNumberOfScalarComponents(); size_t planeSize = dim[1] * lineSize; size_t size = dim[2] * planeSize; - *data = new unsigned char[size]; - - unsigned char *src = (unsigned char *)img->GetScalarPointer(); - unsigned char *dst = *data + planeSize - lineSize; - for (int plane = 0; plane < dim[2]; plane++) + if( size>0 ) { - for (int line = 0; line < dim[1]; line++) - { - // Copy one line at proper destination: - memcpy((void*)dst, (void*)src, lineSize); + *data = new unsigned char[size]; - src += lineSize; - dst -= lineSize; + image->GetIncrements(inc); + unsigned char *src = (unsigned char *)image->GetScalarPointerForExtent(extent); + unsigned char *dst = *data + planeSize - lineSize; + for (int plane = extent[4]; plane <= extent[5]; plane++) + { + for (int line = extent[2]; line <= extent[3]; line++) + { + // Copy one line at proper destination: + memcpy((void*)dst, (void*)src, lineSize); + + src += inc[1] * image->GetScalarSize(); + dst -= lineSize; + } + dst += 2 * planeSize; } - dst += 2 * planeSize; + } + else + { + *data = NULL; } return size; } /** - * Set the datas informations in the file + * Set the medical informations in the file, based on the user passed + * vtkMedicalImageProperties */ -void SetImageInformation(gdcm::File *file,vtkImageData *image) +#if (VTK_MAJOR_VERSION >= 5) +void SetMedicalImageInformation(GDCM_NAME_SPACE::FileHelper *file, vtkMedicalImageProperties *medprop) +{ + // For now only do: + // PatientName, PatientID, PatientAge, PatientSex, PatientBirthDate, StudyID + std::ostringstream str; + if( medprop ) + { + if (medprop->GetPatientName()) + { + str.str(""); + str << medprop->GetPatientName(); + file->InsertEntryString(str.str(),0x0010,0x0010,"PN"); // PN 1 Patient's Name + } + + if (medprop->GetPatientID()) + { + str.str(""); + str << medprop->GetPatientID(); + file->InsertEntryString(str.str(),0x0010,0x0020,"LO"); // LO 1 Patient ID + } + + if (medprop->GetPatientAge()) + { + str.str(""); + str << medprop->GetPatientAge(); + file->InsertEntryString(str.str(),0x0010,0x1010,"AS"); // AS 1 Patient's Age + } + + if (medprop->GetPatientSex()) + { + str.str(""); + str << medprop->GetPatientSex(); + file->InsertEntryString(str.str(),0x0010,0x0040,"CS"); // CS 1 Patient's Sex + } + + if (medprop->GetPatientBirthDate()) + { + str.str(""); + str << medprop->GetPatientBirthDate(); + file->InsertEntryString(str.str(),0x0010,0x0030,"DA"); // DA 1 Patient's Birth Date + } + + if (medprop->GetStudyID()) + { + str.str(""); + str << medprop->GetStudyID(); + file->InsertEntryString(str.str(),0x0020,0x0010,"SH"); // SH 1 Study ID + } + } +} +#endif + +/** + * Set the data informations in the file + */ +void SetImageInformation(GDCM_NAME_SPACE::FileHelper *file, vtkImageData *image) { std::ostringstream str; // Image size - int *dim = image->GetDimensions(); + int *extent = image->GetUpdateExtent(); + int dim[3] = {extent[1]-extent[0]+1, + extent[3]-extent[2]+1, + extent[5]-extent[4]+1}; str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0011); + str << dim[0]; + file->InsertEntryString(str.str(),0x0028,0x0011,"US"); // Columns str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0010); + str << dim[1]; + file->InsertEntryString(str.str(),0x0028,0x0010,"US"); // Rows if(dim[2]>1) { str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0012); + str << dim[2]; + //file->Insert(str.str(),0x0028,0x0012); // Planes + file->InsertEntryString(str.str(),0x0028,0x0008,"IS"); // Number of Frames } // Pixel type str.str(""); - str<GetScalarSize()*8; - file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0100); - file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0101); + str << image->GetScalarSize()*8; + file->InsertEntryString(str.str(),0x0028,0x0100,"US"); // Bits Allocated + file->InsertEntryString(str.str(),0x0028,0x0101,"US"); // Bits Stored str.str(""); - str<GetScalarSize()*8-1; - file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0102); + str << image->GetScalarSize()*8-1; + file->InsertEntryString(str.str(),0x0028,0x0102,"US"); // High Bit // Pixel Repr // FIXME : what do we do when the ScalarType is // VTK_UNSIGNED_INT or VTK_UNSIGNED_LONG str.str(""); - if( image->GetScalarType() == VTK_UNSIGNED_CHAR || + if( image->GetScalarType() == VTK_UNSIGNED_CHAR || image->GetScalarType() == VTK_UNSIGNED_SHORT || - image->GetScalarType() == VTK_UNSIGNED_INT || + image->GetScalarType() == VTK_UNSIGNED_INT || image->GetScalarType() == VTK_UNSIGNED_LONG ) { - str<<"0"; // Unsigned + str << "0"; // Unsigned } else { - str<<"1"; // Signed + str << "1"; // Signed } - file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0103); + file->InsertEntryString(str.str(),0x0028,0x0103,"US"); // Pixel Representation // Samples per pixel str.str(""); - str<GetNumberOfScalarComponents(); - file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0002); + str << image->GetNumberOfScalarComponents(); + file->InsertEntryString(str.str(),0x0028,0x0002,"US"); // Samples per Pixel + + /// \todo : Spacing Between Slices is meaningfull ONLY for CT an MR modality + /// We should perform some checkings before forcing the Entry creation // Spacing - double *sp = image->GetSpacing(); + vtkFloatingPointType *sp = image->GetSpacing(); str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0030); + // We are about to enter floating point value. + // By default ostringstream are smart and don't do fixed point + // thus forcing to fixed point value + str.setf( std::ios::fixed ); + str << sp[1] << "\\" << sp[0]; + file->InsertEntryString(str.str(),0x0028,0x0030,"DS"); // Pixel Spacing str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0018,0x0088); + str << sp[2]; + file->InsertEntryString(str.str(),0x0018,0x0088,"DS"); // Spacing Between Slices // Origin - double *org = image->GetOrigin(); + vtkFloatingPointType *org = image->GetOrigin(); + + /// \todo : Image Position Patient is meaningfull ONLY for CT an MR modality + /// We should perform some checkings before forcing the Entry creation str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0020,0x0032); + str << org[0] << "\\" << org[1] << "\\" << org[2]; + file->InsertEntryString(str.str(),0x0020,0x0032,"DS"); // Image Position Patient + str.unsetf( std::ios::fixed ); //done with floating point values // Window / Level - double *rng=image->GetScalarRange(); + vtkFloatingPointType *rng = image->GetScalarRange(); str.str(""); - str<ReplaceOrCreateByNumber(str.str(),0x0028,0x1051); + str << rng[1]-rng[0]; + file->InsertEntryString(str.str(),0x0028,0x1051,"DS"); // Window Width str.str(""); - str<<(rng[1]+rng[0])/2.0; - file->ReplaceOrCreateByNumber(str.str(),0x0028,0x1050); + str << (rng[1]+rng[0])/2.0; + file->InsertEntryString(str.str(),0x0028,0x1050,"DS"); // Window Center // Pixels unsigned char *data; size_t size = ReverseData(image,&data); - file->SetImageData(data,size); + file->SetUserData(data,size); } /** * Write of the files * The call to this method is recursive if there is some files to write */ -void vtkGdcmWriter::RecursiveWrite(int axis, vtkImageData *image, ofstream *file) +void vtkGdcmWriter::RecursiveWrite(int axis, vtkImageData *image, + ofstream *file) { if(file) { - vtkErrorMacro(<< "File musn't be opened"); + vtkErrorMacro( << "File must not be open"); return; } if( image->GetScalarType() == VTK_FLOAT || image->GetScalarType() == VTK_DOUBLE ) { - vtkErrorMacro(<< "Bad input type. Scalar type musn't be of type " - << "VTK_FLOAT or VTKDOUBLE (found:" - << image->GetScalarTypeAsString()); + vtkErrorMacro(<< "Bad input type. Scalar type must not be of type " + << "VTK_FLOAT or VTK_DOUBLE (found:" + << image->GetScalarTypeAsString() << ")" ); + return; + } + + RecursiveWrite(axis,image, image, file); + //WriteDcmFile(this->FileName,image); +} + +void vtkGdcmWriter::RecursiveWrite(int axis, vtkImageData *cache, + vtkImageData *image, ofstream *file) +{ + int idx, min, max; + + // if the file is already open then just write to it + if( file ) + { + vtkErrorMacro( << "File musn't be open"); + return; + } + + // if we need to open another slice, do it + if( (axis + 1) == this->FileDimensionality ) + { + // determine the name + if (this->FileName) + { + sprintf(this->InternalFileName, "%s", this->FileName); + } + else + { + if (this->FilePrefix) + { + sprintf(this->InternalFileName, this->FilePattern, + this->FilePrefix, this->FileNumber); + } + else + { + sprintf(this->InternalFileName, this->FilePattern,this->FileNumber); + } +// Remove this code in case user is using VTK 4.2... +#if !(VTK_MAJOR_VERSION == 4 && VTK_MINOR_VERSION == 2) + if (this->FileNumber < this->MinimumFileNumber) + { + this->MinimumFileNumber = this->FileNumber; + } + else if (this->FileNumber > this->MaximumFileNumber) + { + this->MaximumFileNumber = this->FileNumber; + } +#endif + } + + // Write the file + WriteDcmFile(this->InternalFileName,image); + ++this->FileNumber; return; } - WriteFile(this->FileName,image); + // if the current region is too high a dimension for the file + // the we will split the current axis + cache->GetAxisUpdateExtent(axis, min, max); + + // if it is the y axis then flip by default + if (axis == 1 && !this->FileLowerLeft) + { + for(idx = max; idx >= min; idx--) + { + cache->SetAxisUpdateExtent(axis, idx, idx); + this->RecursiveWrite(axis - 1, cache, image, file); + } + } + else + { + for(idx = min; idx <= max; idx++) + { + cache->SetAxisUpdateExtent(axis, idx, idx); + this->RecursiveWrite(axis - 1, cache, image, file); + } + } + + // restore original extent + cache->SetAxisUpdateExtent(axis, min, max); } -void vtkGdcmWriter::WriteFile(char *fileName,vtkImageData *image) +void vtkGdcmWriter::WriteDcmFile(char *fileName, vtkImageData *image) { + GDCM_NAME_SPACE::FileHelper *dcmFile; + if ( GdcmFile != 0) + dcmFile = GDCM_NAME_SPACE::FileHelper::New(GdcmFile); + else + dcmFile = GDCM_NAME_SPACE::FileHelper::New(); + // From here, the write of the file begins - gdcm::File *dcmFile = new gdcm::File(); + // Set the medical informations: +#if (VTK_MAJOR_VERSION >= 5) + SetMedicalImageInformation(dcmFile, this->MedicalImageProperties); +#endif + // Set the image informations - SetImageInformation(dcmFile,image); + SetImageInformation(dcmFile, image); // Write the image - if(!dcmFile->Write(FileName)) + switch(this->WriteType) { - vtkErrorMacro(<< "File " << this->FileName << "couldn't be written by " - << " the gdcm library"); - std::cerr<<"not written \n"; + case VTK_GDCM_WRITE_TYPE_EXPLICIT_VR : + dcmFile->SetWriteTypeToDcmExplVR(); + break; + case VTK_GDCM_WRITE_TYPE_IMPLICIT_VR : + dcmFile->SetWriteTypeToDcmImplVR(); + break; + case VTK_GDCM_WRITE_TYPE_ACR : + dcmFile->SetWriteTypeToAcr(); + break; + case VTK_GDCM_WRITE_TYPE_ACR_LIBIDO : + dcmFile->SetWriteTypeToAcrLibido(); + break; + default : + dcmFile->SetWriteTypeToDcmExplVR(); } - - delete dcmFile; + + dcmFile->SetContentType((GDCM_NAME_SPACE::ImageContentType)ContentType); + + if(!dcmFile->Write(fileName)) + { + vtkErrorMacro( << "File " << this->FileName << "cannot be written by " + << " the gdcm library"); + } + // Clean up + if( dcmFile->GetUserData() && dcmFile->GetUserDataSize()>0 ) + { + delete[] dcmFile->GetUserData(); + } + dcmFile->Delete(); } //-----------------------------------------------------------------------------