From 297fe0ed9c4b5535b92c36615acff0b059209ed3 Mon Sep 17 00:00:00 2001 From: frog Date: Wed, 16 Apr 2003 21:37:59 +0000 Subject: [PATCH 1/1] * More memmory link related corrections and documentation fixes. Notes on valgrind: - maximum info is obtained with a command of the form: valgrind --leak-check=yes --leak-resolution=high --num-callers=40 --show-reachable=yes PrintHeader - the remaining reachable blocks seem to come from the STL allocation scheme through the usage of map and list. It looks like this memory cannot be freed but it is not a memory leak (in fact further invocation to the STL would recollect the unused memory allthough it cannot explicitely be freed). * gdcmPython/demo/vtkGdcmDemo.py added: this is a small demo of displaying an image parsed with gdcm and displayed with VTK. Note: some images don't seem to work e.g. python vtkGdcmDemo.py ../../Data/US-RGB-8-esopecho.dcm * src/gdcmHeader.x: dicom_vr and Dicts are not class members anymore. Allthough this weakens the semantics, it is a ditch attempt to make gdcm more thread friendly. --- Frog --- ChangeLog | 16 +++ gdcmPython/demo/vtkGdcmDemo.py | 205 +++++++++++++++++++++++++++++++++ gdcmPython/gdcm.i | 10 ++ src/gdcmFile.cxx | 2 +- src/gdcmHeader.cxx | 9 +- src/gdcmHeader.h | 7 +- 6 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 gdcmPython/demo/vtkGdcmDemo.py diff --git a/ChangeLog b/ChangeLog index c3da4560..c7ea5813 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,21 @@ 2003-04-16 Eric Boix with JPR * More memmory link related corrections and documentation fixes. + Notes on valgrind: + - maximum info is obtained with a command of the form: + valgrind --leak-check=yes --leak-resolution=high --num-callers=40 + --show-reachable=yes PrintHeader + - the remaining reachable blocks seem to come from the STL + allocation scheme through the usage of map and list. It looks + like this memory cannot be freed but it is not a memory leak + (in fact further invocation to the STL would recollect the + unused memory allthough it cannot explicitely be freed). + * gdcmPython/demo/vtkGdcmDemo.py added: this is a small demo + of displaying an image parsed with gdcm and displayed with VTK. + Note: some images don't seem to work e.g. + python vtkGdcmDemo.py ../../Data/US-RGB-8-esopecho.dcm + * src/gdcmHeader.x: dicom_vr and Dicts are not class members anymore. + Allthough this weakens the semantics, it is a ditch attempt to + make gdcm more thread friendly. 2003-04-15 Eric Boix with JPR * Memory link hunt (by using valgrind through the command diff --git a/gdcmPython/demo/vtkGdcmDemo.py b/gdcmPython/demo/vtkGdcmDemo.py new file mode 100644 index 00000000..142066a1 --- /dev/null +++ b/gdcmPython/demo/vtkGdcmDemo.py @@ -0,0 +1,205 @@ +## A small demo that displays with VTK an image parsed with gdcm. +import sys +import vtk +from gdcmPython import gdcmHeader + +class vtkHistogram: + '''Some medical images might have a poor dynamic. This is because + some additional visual tags-like the patient name- are sometimes + written/drawn on top of the image : to avoid interference with + the image itself, the level used for this additional text is + usually way above the maximum level of the image. Hence we use + some cumulative histograme die-hard technique to remove some + arbitrary small portion of the dynamic.''' + def __init__(self, imagereader): + self.vtkReader = imagereader + self.__Histo = None + self.__CumulHisto = None + self.__LevelMin = self.vtkReader.GetOutput().GetScalarRange()[0] + self.__LevelMax = self.vtkReader.GetOutput().GetScalarRange()[1] + self.__NumberOfBins = 255 # See VTKImageAccumulate + self.__Spacing = (self.__LevelMax - self.__LevelMin)/self.__NumberOfBins + + def ComputeHisto(self): + self.__Histo = vtk.vtkImageAccumulate() + self.__Histo.SetComponentExtent(0, self.__NumberOfBins, 0, 0, 0, 0) + self.__Histo.SetInput(self.vtkReader.GetOutput()) + self.__Histo.SetComponentSpacing(self.__Spacing, 0.0, 0.0) + self.__Histo.Update() + + def ComputeCumulativeHisto(self): + ''' Compyte the Cumulative histogram of the image.''' + if not self.__Histo: + self.ComputeHisto() + self.__CumulHisto = [] + histo = self.__Histo.GetOutput() + self.__CumulHisto.append(int(histo.GetScalarComponentAsFloat(0,0,0,0))) + for i in range(1, self.__NumberOfBins): + value = int(histo.GetScalarComponentAsFloat(i,0,0,0)) + self.__CumulHisto.append( self.__CumulHisto[i-1] + value) + + def GetTruncateLevels(self, LostPercentage): + ''' Compute the level below which (respectively above which) + the LostPercentage percentage of the pixels shall be disregarded. + ''' + if LostPercentage < 0.0 or LostPercentage >1.0: + print " vtkHistogram::GetTruncateLevels: defaulting to 5%" + LostPercentage = 0.05 + if not self.__CumulHisto: + self.ComputeCumulativeHisto() + NumPixels = self.__CumulHisto[self.__NumberOfBins - 1] + NumLostPixels = NumPixels * LostPercentage + AboveLevel = None + BelowLevel = None + # FIXME : not really clean loop... + for i in range(0, self.__NumberOfBins-1): + if not BelowLevel: + if self.__CumulHisto[i] > NumLostPixels: + BelowLevel = self.__LevelMin + self.__Spacing * i + if not AboveLevel: + if self.__CumulHisto[i] > NumPixels - NumLostPixels: + AboveLevel = self.__LevelMin + self.__Spacing * i + if not AboveLevel or not BelowLevel: + print " vtkHistogram::GetTruncateLevels: Mistakes were made..." + return BelowLevel, AboveLevel + +class vtkGdcmImage(gdcmHeader): + ''' All informations required for VTK display + * VTK pipeline + * Lut (or associated Window/Level reductive control parameters)''' + def __init__(self, filename): + gdcmHeader.__init__(self, filename) + self.filename = filename + # LUT reductive parameters + self.__LevelMin = 0 # Minimum value of pixel intensity + self.__LevelMax = 0 # Maximun value of pixel intensity + self.__Level = 0 # Current Level value (treshold below + # which the image is represented as black) + self.__Window = 0 # Width of displayed pixels (linearly). + # Above Level + Window pixel are represented + # as white. + self.__VTKplaneActor = None + self.vtkSetup() + + def VTKreaderSetup(self): + self.__VTKReader = vtk.vtkImageReader() + self.__VTKReader.SetFileName(self.filename) + type = self.GetPixelType() + if type == "8U": + self.__VTKReader.SetDataScalarTypeToUnsignedChar() + elif type == "8S": + self.__VTKReader.SetDataScalarTypeTodChar() + elif type == "16U": + self.__VTKReader.SetDataScalarTypeToUnsignedShort() + elif type == "16S": + self.__VTKReader.SetDataScalarTypeToShort() + self.__VTKReader.SetDataByteOrderToLittleEndian() + #self.__VTKReader.SetDataByteOrderToBigEndian() + else: + print "vtkGdcmImage::VTKreaderSetup: unkown encoding:", type + sys.exit() + self.__VTKReader.SetHeaderSize(self.GetPixelOffset()) + self.__VTKReader.SetDataExtent (0, self.GetXSize()-1, + 0, self.GetYSize()-1, + 1, 1) + self.__VTKReader.UpdateWholeExtent() + + def vtkSetup(self, orgx = -0.5, orgy = -0.5, + ix = 0.5, iy = -0.5, jx = -0.5, jy = 0.5): + # ImageReader ---> Texture \ + # | ==> PlaneActor + # PlaneSource ---> PolyDataMapper / + self.VTKreaderSetup() + ### LookupTable + self.__VTKtable = vtk.vtkLookupTable() + self.__VTKtable.SetNumberOfColors(1000) + self.__VTKtable.SetTableRange(0,1000) + self.CallibrateWindowLevel() # calls the SetTableRange + self.__VTKtable.SetSaturationRange(0,0) + self.__VTKtable.SetHueRange(0,1) + self.__VTKtable.SetValueRange(0,1) + self.__VTKtable.SetAlphaRange(1,1) + self.__VTKtable.Build() + ### Texture + self.__VTKtexture = vtk.vtkTexture() + self.__VTKtexture.SetInput(self.__VTKReader.GetOutput()) + self.__VTKtexture.InterpolateOn() + self.__VTKtexture.SetLookupTable(self.__VTKtable) + ### PlaneSource + self.__VTKplane = vtk.vtkPlaneSource() + self.__VTKplane.SetOrigin( orgx, orgy, 0.0) + self.__VTKplane.SetPoint1( ix, iy, 0.0) + self.__VTKplane.SetPoint2( jx, jy, 0.0) + ### PolyDataMapper + self.__VTKplaneMapper = vtk.vtkPolyDataMapper() + self.__VTKplaneMapper.SetInput(self.__VTKplane.GetOutput()) + ### Actor + self.__VTKplaneActor = vtk.vtkActor() + self.__VTKplaneActor.SetTexture(self.__VTKtexture) + self.__VTKplaneActor.SetMapper(self.__VTKplaneMapper) + self.__VTKplaneActor.PickableOn() + + def CallibrateWindowLevel(self): + vtkreader = self.__VTKReader + self.__LevelMin = vtkreader.GetOutput().GetScalarRange()[0] + self.__LevelMax = vtkreader.GetOutput().GetScalarRange()[1] + histo = vtkHistogram(vtkreader) + self.__Level, above = histo.GetTruncateLevels(0.05) + self.__Window = above - self.__Level + self.__VTKtable.SetTableRange(self.__Level, + self.__Level + self.__Window) + + def GetVTKActor(self): + return self.__VTKplaneActor + +###################################################################### +import os +from gdcmPython import GDCM_DATA_PATH + +### Get filename from command line or default it +try: + FileName = sys.argv[1] +except IndexError: + FileName = os.path.join(GDCM_DATA_PATH, "test.acr") + +if not os.path.isfile(FileName): + print "Cannot open file ", FileName + sys.exit() + +### The default vtkImageReader is unable to read some type of images: +check = gdcmHeader(FileName) +if not check.IsReadable(): + print "The ", FileName, " file is not " + print " readable with gdcm. Sorry." + sys.exit() +check = check.GetPubElVal() +try: + BitsAlloc = check["Bits Allocated"] + if len(BitsAlloc) == 0 or BitsAlloc == "gdcm::Unfound": + raise KeyError +except KeyError: + print "Gdcm couldn't find the Bits Allocated Dicom tag in file ", FileName + sys.exit() +try: + BitsStored = check["Bits Stored"] + if len(BitsStored) == 0 or BitsStored == "gdcm::Unfound": + raise KeyError +except KeyError: + print "Gdcm couldn't find the Bits Stored Dicom tag in file ", FileName + sys.exit() +if BitsAlloc != BitsStored: + print "vtkImageReader cannot read the file ", FileName + print " because the Bits Allocated and the Bits stored don't match." + sys.exit() + +### Display in a vtk RenderWindow +image = vtkGdcmImage(FileName) +ren = vtk.vtkRenderer() +renwin = vtk.vtkRenderWindow() +renwin.AddRenderer(ren) +iren = vtk.vtkRenderWindowInteractor() +iren.SetRenderWindow(renwin) +ren.AddActor(image.GetVTKActor()) +ren.SetBackground(0,0,0.5) +renwin.Render() +iren.Start() diff --git a/gdcmPython/gdcm.i b/gdcmPython/gdcm.i index f89537bb..41d5ee20 100644 --- a/gdcmPython/gdcm.i +++ b/gdcmPython/gdcm.i @@ -20,6 +20,7 @@ void EatLeadingAndTrailingSpaces(string & s) { typedef unsigned short guint16; typedef unsigned int guint32; +//////////////////////////////////////////////////////////////////////////// %typemap(out) list * { PyObject* NewItem = (PyObject*)0; PyObject* NewList = PyList_New(0); // The result of this typemap @@ -31,6 +32,7 @@ typedef unsigned int guint32; $result = NewList; } +//////////////////////////////////////////////////////////////////////////// // Convert a c++ hash table in a python native dictionary %typemap(out) map > * { PyObject* NewDict = PyDict_New(); // The result of this typemap @@ -55,6 +57,7 @@ typedef unsigned int guint32; $result = NewDict; } +//////////////////////////////////////////////////////////////////////////// // Convert a c++ hash table in a python native dictionary %typemap(out) TagElValueHT & { PyObject* NewDict = PyDict_New(); // The result of this typemap @@ -84,6 +87,13 @@ typedef unsigned int guint32; $result = NewDict; } +//////////////////////////////////////////////////////////////////////////// +// Deals with function returning a C++ string. +%typemap(out) string { + $result = PyString_FromString(($1).c_str()); +} + +//////////////////////////////////////////////////////////////////////////// %include "gdcmCommon.h" %include "gdcmDictEntry.h" %include "gdcmDict.h" diff --git a/src/gdcmFile.cxx b/src/gdcmFile.cxx index c103e111..5e39b701 100644 --- a/src/gdcmFile.cxx +++ b/src/gdcmFile.cxx @@ -280,7 +280,7 @@ return; * \brief TODO JPR * \warning doit-etre etre publique ? FIXME JPR * - * @param Data TODO JPR + * @param inData TODO JPR * @param ExpectedSize TODO JPR * * @return TODO JPR diff --git a/src/gdcmHeader.cxx b/src/gdcmHeader.cxx index 82c31a0f..10404615 100644 --- a/src/gdcmHeader.cxx +++ b/src/gdcmHeader.cxx @@ -18,14 +18,9 @@ // Refer to gdcmHeader::SetMaxSizeLoadElementValue() #define _MaxSizeLoadElementValue_ 1024 -gdcmVR * gdcmHeader::dicom_vr = (gdcmVR*)0; -gdcmDictSet * gdcmHeader::Dicts = (gdcmDictSet*)0; - void gdcmHeader::Initialise(void) { - if (!gdcmHeader::dicom_vr) - gdcmHeader::dicom_vr = gdcmGlobal::GetVR(); - if (!gdcmHeader::Dicts) - gdcmHeader::Dicts = gdcmGlobal::GetDicts(); + dicom_vr = gdcmGlobal::GetVR(); + Dicts = gdcmGlobal::GetDicts(); RefPubDict = Dicts->GetDefaultPubDict(); RefShaDict = (gdcmDict*)0; } diff --git a/src/gdcmHeader.h b/src/gdcmHeader.h index 2741b3ed..b5505671 100644 --- a/src/gdcmHeader.h +++ b/src/gdcmHeader.h @@ -37,10 +37,9 @@ class GDCM_EXPORT gdcmHeader { private: /// Pointer to the Value Representation Hash Table which contains all /// the VR of the DICOM version3 public dictionary. - static gdcmVR *dicom_vr; - - /// Global dictionary container - static gdcmDictSet* Dicts; + gdcmVR *dicom_vr; // Not a class member for thread-safety reasons + /// Pointer to global dictionary container + gdcmDictSet* Dicts; // Not a class member for thread-safety reasons /// Public dictionary used to parse this header gdcmDict* RefPubDict; /// Optional "shadow dictionary" (private elements) used to parse this -- 2.45.1