From 2ca09ddbc04e1ec02aa49e8815925cceb49b2402 Mon Sep 17 00:00:00 2001 From: regrain Date: Tue, 7 Dec 2004 17:28:49 +0000 Subject: [PATCH] * vtk/vtkGdcmWriter.[h|cxx] : add a first version of vtkGdcmWriter * src/gdcmValEntry.cxx : bug fix when setting the value. Problems of odd length * src/gdcmHeader.cxx : Remove a useless call to Util::DicomString * Add vtkGdcmWriter example and test -- BeNours --- ChangeLog | 7 ++ Testing/CMakeLists.txt | 1 + src/gdcmDicomDir.cxx | 5 +- src/gdcmFile.cxx | 41 +++++++-- src/gdcmFile.h | 12 ++- src/gdcmHeader.cxx | 16 ++-- src/gdcmValEntry.cxx | 15 +++- vtk/CMakeLists.txt | 12 +++ vtk/vtkGdcmReader.cxx | 8 +- vtk/vtkGdcmWriter.cxx | 197 +++++++++++++++++++++++++++++++++++++++++ vtk/vtkGdcmWriter.h | 38 ++++++++ vtk/vtkWriteDicom.cxx | 74 ++++++++++++++++ 12 files changed, 400 insertions(+), 26 deletions(-) create mode 100644 vtk/vtkGdcmWriter.cxx create mode 100644 vtk/vtkGdcmWriter.h create mode 100644 vtk/vtkWriteDicom.cxx diff --git a/ChangeLog b/ChangeLog index 3557fc48..a4db53cd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2004-12-07 Benoit Regrain + * vtk/vtkGdcmWriter.[h|cxx] : add a first version of vtkGdcmWriter + * src/gdcmValEntry.cxx : bug fix when setting the value. Problems of odd + length + * src/gdcmHeader.cxx : Remove a useless call to Util::DicomString + * Add vtkGdcmWriter example and test + 2004-12-07 Benoit Regrain * Test/TestUtil.cxx : reformat the source code * vtk/vtkGdcmReader.cxx : remove and change prints diff --git a/Testing/CMakeLists.txt b/Testing/CMakeLists.txt index 011b9a3b..268f52fe 100644 --- a/Testing/CMakeLists.txt +++ b/Testing/CMakeLists.txt @@ -41,6 +41,7 @@ IF (GDCM_DATA_ROOT) ) SET(TEST_SOURCES ${TEST_SOURCES} ShowDicom.cxx + TestWriteWithVTK.cxx ) ENDIF(GDCM_VTK) ENDIF (GDCM_DATA_ROOT) diff --git a/src/gdcmDicomDir.cxx b/src/gdcmDicomDir.cxx index 7ab7f7d6..e5cc92d0 100644 --- a/src/gdcmDicomDir.cxx +++ b/src/gdcmDicomDir.cxx @@ -3,8 +3,8 @@ Program: gdcm Module: $RCSfile: gdcmDicomDir.cxx,v $ Language: C++ - Date: $Date: 2004/12/07 13:39:33 $ - Version: $Revision: 1.88 $ + Date: $Date: 2004/12/07 17:28:50 $ + Version: $Revision: 1.89 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -422,7 +422,6 @@ void DicomDir::CreateDicomDirChainedList(std::string const & path) break; } -std::cerr<<"File : "<c_str()<c_str() ); if( !header ) { diff --git a/src/gdcmFile.cxx b/src/gdcmFile.cxx index 43804b36..03df5b70 100644 --- a/src/gdcmFile.cxx +++ b/src/gdcmFile.cxx @@ -3,8 +3,8 @@ Program: gdcm Module: $RCSfile: gdcmFile.cxx,v $ Language: C++ - Date: $Date: 2004/12/07 13:39:33 $ - Version: $Revision: 1.173 $ + Date: $Date: 2004/12/07 17:28:50 $ + Version: $Revision: 1.174 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -34,6 +34,26 @@ typedef std::pair IterHT; //------------------------------------------------------------------------- // Constructor / Destructor +/** + * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3 + * file (Header only deals with the ... header) + * Opens (in read only and when possible) an existing file and checks + * for DICOM compliance. Returns NULL on failure. + * It will be up to the user to load the pixels into memory + * (see GetImageData, GetImageDataRaw) + * \note the in-memory representation of all available tags found in + * the DICOM header is post-poned to first header information access. + * This avoid a double parsing of public part of the header when + * one sets an a posteriori shadow dictionary (efficiency can be + * seen as a side effect). + */ +File::File( ) +{ + HeaderInternal = new Header( ); + SelfHeader = true; + Initialise(); +} + /** * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3 * file (Header only deals with the ... header) @@ -82,7 +102,7 @@ File::File(std::string const & filename ) void File::Initialise() { WriteMode = WMODE_DECOMPRESSED; - WriteType = ImplicitVR; + WriteType = ExplicitVR; PixelReadConverter = new PixelReadConvert; PixelWriteConverter = new PixelWriteConvert; @@ -383,8 +403,19 @@ bool File::Write(std::string const& fileName) bool File::SetEntryByNumber(std::string const& content, uint16_t group, uint16_t element) { - HeaderInternal->SetEntryByNumber(content,group,element); - return true; + return HeaderInternal->SetEntryByNumber(content,group,element); +} + +bool File::SetEntryByNumber(uint8_t* content, int lgth, + uint16_t group, uint16_t element) +{ + return HeaderInternal->SetEntryByNumber(content,lgth,group,element); +} + +bool File::ReplaceOrCreateByNumber(std::string const& content, + uint16_t group, uint16_t element) +{ + return HeaderInternal->ReplaceOrCreateByNumber(content,group,element) != NULL; } /** diff --git a/src/gdcmFile.h b/src/gdcmFile.h index f7248418..23f87ff6 100644 --- a/src/gdcmFile.h +++ b/src/gdcmFile.h @@ -3,8 +3,8 @@ Program: gdcm Module: $RCSfile: gdcmFile.h,v $ Language: C++ - Date: $Date: 2004/12/04 09:41:02 $ - Version: $Revision: 1.84 $ + Date: $Date: 2004/12/07 17:28:50 $ + Version: $Revision: 1.85 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -45,6 +45,7 @@ public: }; public: + File( ); File( Header* header ); File( std::string const& filename ); @@ -72,7 +73,14 @@ public: bool Write(std::string const& fileName); bool SetEntryByNumber(std::string const& content, + uint16_t group, uint16_t element); + bool SetEntryByNumber(uint8_t* content, int lgth, + uint16_t group, uint16_t element); + bool ReplaceOrCreateByNumber(std::string const& content, uint16_t group, uint16_t element); + bool ReplaceOrCreateByNumber(uint8_t* binArea, int lgth, + uint16_t group, uint16_t elem); + uint8_t* GetLutRGBA(); // Write mode diff --git a/src/gdcmHeader.cxx b/src/gdcmHeader.cxx index 9bef4bd0..e15c9c28 100644 --- a/src/gdcmHeader.cxx +++ b/src/gdcmHeader.cxx @@ -3,8 +3,8 @@ Program: gdcm Module: $RCSfile: gdcmHeader.cxx,v $ Language: C++ - Date: $Date: 2004/12/07 13:39:33 $ - Version: $Revision: 1.213 $ + Date: $Date: 2004/12/07 17:28:50 $ + Version: $Revision: 1.214 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -1311,7 +1311,7 @@ void Header::InitializeDefaultHeader() std::string uid = Util::CreateUniqueUID(); static DICOM_DEFAULT_VALUE defaultvalue[] = { - { "76", 0x0002, 0x0000}, // MetaElementGroup Length // FIXME: how to recompute ? + { "76 ", 0x0002, 0x0000}, // MetaElementGroup Length // FIXME: how to recompute ? { "1.2.840.10008.5.1.4.1.1.2", 0x0002, 0x0002}, // MediaStorageSOPInstanceUID (CT Image Storage) { uid.c_str(), 0x0002, 0x0012}, // META Implementation Class UID { "ISO_IR 100",0x0008, 0x0005}, // Specific Character Set @@ -1331,10 +1331,9 @@ void Header::InitializeDefaultHeader() { "", 0x0020, 0x0011}, // AcquisitionNumber { "1\\0\\0\\0\\1\\0", 0x0020, 0x0037}, // Image Orientation Patient { "1", 0x0028, 0x0002}, // Samples per pixel 1 or 3 - { "MONOCHROME2",0x0028, 0x0004}, // photochromatic interpretation + { "MONOCHROME1",0x0028, 0x0004}, // photochromatic interpretation // Date and timeG - { date.c_str(), 0x0008, 0x0012 } , // Instance Creation Date { time.c_str(), 0x0008, 0x0013 } , // Instance Creation Time { date.c_str(), 0x0008, 0x0020 } , // Study Date @@ -1352,8 +1351,8 @@ void Header::InitializeDefaultHeader() { "64", 0x0028, 0x0010 } , // nbRows { "64", 0x0028, 0x0011 } , // nbCols { "16", 0x0028, 0x0100 } , // BitsAllocated 8 or 16 - { "12", 0x0028, 0x0101 } , // BitsStored 8 or 12 - { "11", 0x0028, 0x0102 } , // HighBit 7 or 11 + { "12", 0x0028, 0x0101 } , // BitsStored 8 or 12 or 16 + { "11", 0x0028, 0x0102 } , // HighBit 7 or 11 or 15 { "0", 0x0028, 0x0103 } , // Pixel Representation 0(unsigned) or 1(signed) { "1000.0", 0x0028, 0x1051 } , // Window Width { "500.0", 0x0028, 0x1050 } , // Window Center @@ -1371,8 +1370,7 @@ void Header::InitializeDefaultHeader() DICOM_DEFAULT_VALUE current = defaultvalue[i]; while( current.value ) { - std::string value = Util::DicomString( current.value ); //pad the string properly - ReplaceOrCreateByNumber(value, current.group, current.elem); + ReplaceOrCreateByNumber(current.value, current.group, current.elem); current = defaultvalue[++i]; } } diff --git a/src/gdcmValEntry.cxx b/src/gdcmValEntry.cxx index 270fdd3a..a3f17d80 100644 --- a/src/gdcmValEntry.cxx +++ b/src/gdcmValEntry.cxx @@ -3,8 +3,8 @@ Program: gdcm Module: $RCSfile: gdcmValEntry.cxx,v $ Language: C++ - Date: $Date: 2004/12/07 13:39:33 $ - Version: $Revision: 1.39 $ + Date: $Date: 2004/12/07 17:28:50 $ + Version: $Revision: 1.40 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -188,13 +188,22 @@ void ValEntry::SetValue(std::string const & val) { // for multivaluated items l = (Util::CountSubstring(val, "\\") + 1) * 2; + SetValueOnly(val); } else if( vr == "UL" || vr == "SL" ) { // for multivaluated items l = (Util::CountSubstring(val, "\\") + 1) * 4;; + SetValueOnly(val); + } + else + { + std::string finalVal = Util::DicomString( val.c_str() ); + assert( !(finalVal.size() % 2) ); + + l = finalVal.length(); + SetValueOnly(finalVal); } - SetValueOnly(val); } else { diff --git a/vtk/CMakeLists.txt b/vtk/CMakeLists.txt index c3b68cc2..69028cc6 100644 --- a/vtk/CMakeLists.txt +++ b/vtk/CMakeLists.txt @@ -13,6 +13,7 @@ INCLUDE_DIRECTORIES( SET(VTKGDCM_LIB_SRCS vtkGdcmReader.cxx + vtkGdcmWriter.cxx ) #----------------------------------------------------------------------------- @@ -46,6 +47,17 @@ TARGET_LINK_LIBRARIES(vtkgdcmViewer vtkRendering ) +#----------------------------------------------------------------------------- +SET(vtkWriteDicom_SOURCES + vtkWriteDicom.cxx +) +ADD_EXECUTABLE(vtkWriteDicom ${vtkWriteDicom_SOURCES}) +TARGET_LINK_LIBRARIES(vtkWriteDicom + vtkgdcm + vtkIO + vtkRendering +) + #----------------------------------------------------------------------------- SET(GdcmToBaseline_SOURCES GdcmToBaseline.cxx diff --git a/vtk/vtkGdcmReader.cxx b/vtk/vtkGdcmReader.cxx index 584305bc..539d5fc8 100644 --- a/vtk/vtkGdcmReader.cxx +++ b/vtk/vtkGdcmReader.cxx @@ -58,7 +58,7 @@ #include #include -vtkCxxRevisionMacro(vtkGdcmReader, "$Revision: 1.61 $"); +vtkCxxRevisionMacro(vtkGdcmReader, "$Revision: 1.62 $"); vtkStandardNewMacro(vtkGdcmReader); //----------------------------------------------------------------------------- @@ -548,9 +548,9 @@ int vtkGdcmReader::CheckFileCoherence() this->DataSpacing[2] = GdcmHeader.GetZSpacing(); //Set image origin - this->DataOrigin[0] = GdcmHeader.GetXOrigin(); - this->DataOrigin[1] = GdcmHeader.GetYOrigin(); - this->DataOrigin[2] = GdcmHeader.GetZOrigin(); + //this->DataOrigin[0] = GdcmHeader.GetXOrigin(); + //this->DataOrigin[1] = GdcmHeader.GetYOrigin(); + //this->DataOrigin[2] = GdcmHeader.GetZOrigin(); } } // End of loop on filename diff --git a/vtk/vtkGdcmWriter.cxx b/vtk/vtkGdcmWriter.cxx new file mode 100644 index 00000000..755b57f8 --- /dev/null +++ b/vtk/vtkGdcmWriter.cxx @@ -0,0 +1,197 @@ +// 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" +#include "gdcmFile.h" +#include "gdcmDebug.h" +#include "vtkGdcmWriter.h" + +#include +#include +#include +#include + +vtkCxxRevisionMacro(vtkGdcmWriter, "$Revision: 1.1 $"); +vtkStandardNewMacro(vtkGdcmWriter); + +//----------------------------------------------------------------------------- +// Constructor / Destructor +vtkGdcmWriter::vtkGdcmWriter() +{ + this->LookupTable = NULL; +} + +vtkGdcmWriter::~vtkGdcmWriter() +{ +} + +//----------------------------------------------------------------------------- +// Print +void vtkGdcmWriter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os,indent); +} + +//----------------------------------------------------------------------------- +// Public + +//----------------------------------------------------------------------------- +// Protected +void SetImageInformation(gdcm::File *file,vtkImageData *image) +{ + std::ostringstream str; + + // Image size + int *dim = image->GetDimensions(); + + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0011); + + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0010); + + if(dim[2]>1) + { + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0012); + } + + // Pixel type + str.str(""); + str<GetScalarSize()*8; + file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0100); + file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0101); + + str.str(""); + str<GetScalarSize()*8-1; + file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0102); + + // Samples per pixel + str.str(""); + str<GetNumberOfScalarComponents(); + file->ReplaceOrCreateByNumber(str.str(),0x0028,0x0002); + std::cout<GetHeader()->GetEntryByNumber(0x0028,0x0002)<<"-"<GetHeader()->GetNumberOfScalarComponents()<<"\n"; + + // Spacing + double *sp = image->GetSpacing(); + + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0028,0x0030); + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0018,0x0088); + + // Origin + double *org = image->GetOrigin(); + + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0020,0x0032); + + // Window / Level + double *rng=image->GetScalarRange(); + + str.str(""); + str<ReplaceOrCreateByNumber(str.str(),0x0028,0x1051); + str.str(""); + str<<(rng[1]+rng[0])/2.0; + file->ReplaceOrCreateByNumber(str.str(),0x0028,0x1050); + + // Pixels + size_t size = dim[0] * dim[1] * dim[2] + * image->GetScalarSize() + * image->GetNumberOfScalarComponents(); + file->SetImageData((unsigned char *)image->GetScalarPointer(),size); +} + +void vtkGdcmWriter::RecursiveWrite(int dim, vtkImageData *region, ofstream *file) +{ + if(file) + { + vtkErrorMacro(<< "File musn't be opened"); + return; + } + + if( region->GetScalarType() == VTK_FLOAT + || region->GetScalarType() == VTK_DOUBLE ) + { + vtkErrorMacro(<< "Bad input type. Scalar type musn't be of type " + << "VTK_FLOAT or VTKDOUBLE (found:" + << region->GetScalarTypeAsString()); + return; + } + + gdcm::File *dcmFile = new gdcm::File(); + + /////////////////////////////////////////////////////////////////////////// + // Set the image informations + SetImageInformation(dcmFile,region); + + /////////////////////////////////////////////////////////////////////////// + // Write the image + if(!dcmFile->Write(this->FileName)) + { + vtkErrorMacro(<< "File " << this->FileName << "couldn't be written by " + << " the gdcm library"); + std::cerr<<"not written \n"; + } + + delete dcmFile; +} + +//----------------------------------------------------------------------------- +// Private + +//----------------------------------------------------------------------------- diff --git a/vtk/vtkGdcmWriter.h b/vtk/vtkGdcmWriter.h new file mode 100644 index 00000000..592daec1 --- /dev/null +++ b/vtk/vtkGdcmWriter.h @@ -0,0 +1,38 @@ +// vtkGdcmWriter.h +//----------------------------------------------------------------------------- +#ifndef __vtkGdcmWriter_h +#define __vtkGdcmWriter_h + +#include "gdcmCommon.h" // To avoid warnings concerning the std + +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +class VTK_EXPORT vtkGdcmWriter : public vtkImageWriter +{ +public: + static vtkGdcmWriter *New(); + vtkTypeRevisionMacro(vtkGdcmWriter, vtkImageWriter); + + void PrintSelf(ostream& os, vtkIndent indent); + + vtkSetObjectMacro(LookupTable,vtkLookupTable); + vtkGetObjectMacro(LookupTable,vtkLookupTable); + +protected: + vtkGdcmWriter(); + ~vtkGdcmWriter(); + + virtual void RecursiveWrite(int dim, vtkImageData *region, ofstream *file); + +private: +// Variables + vtkLookupTable *LookupTable; +}; + +//----------------------------------------------------------------------------- +#endif + diff --git a/vtk/vtkWriteDicom.cxx b/vtk/vtkWriteDicom.cxx new file mode 100644 index 00000000..19452b26 --- /dev/null +++ b/vtk/vtkWriteDicom.cxx @@ -0,0 +1,74 @@ +// This example illustrates how the vtkGdcmWriter vtk class can be +// used in order to: +// +// Usage: +// +//---------------------------------------------------------------------------- +#include + +#include +#include +#include + +#include "vtkGdcmReader.h" +#include "vtkGdcmWriter.h" + +#ifndef vtkFloatingPointType +#define vtkFloatingPointType float +#endif + +//---------------------------------------------------------------------------- +int main(int argc, char *argv[]) +{ + if( argc < 2 ) + return 0; + + vtkGdcmReader *reader = vtkGdcmReader::New(); + reader->AllowLookupTableOff(); + + if( argc == 2 ) + reader->SetFileName( argv[1] ); + else + for(int i=1; i< argc; i++) + reader->AddFileName( argv[i] ); + + reader->Update(); + + vtkImageData *output; + if( reader->GetLookupTable() ) + { + //convert to color: + vtkImageMapToColors *map = vtkImageMapToColors::New (); + map->SetInput (reader->GetOutput()); + map->SetLookupTable (reader->GetLookupTable()); + map->SetOutputFormatToRGB(); + output = map->GetOutput(); + map->Delete(); + } + else + { + output = reader->GetOutput(); + } + + //print debug info: + output->Print( cout ); + + ////////////////////////////////////////////////////////// + // WRITE... + //if you wish you can export dicom to a vtk file + // this file will have the add of .tmp.dcm extention + std::string fileName = argv[1]; + fileName += ".tmp.dcm"; + + vtkGdcmWriter *writer = vtkGdcmWriter::New(); + writer->SetFileName(fileName.c_str()); + writer->SetInput(output); + writer->Write(); + ////////////////////////////////////////////////////////// + + // Clean up + writer->Delete(); + reader->Delete(); + + return 0; +} -- 2.48.1