From 0b36f4932e894a1dde4a80e614755816b4b29218 Mon Sep 17 00:00:00 2001 From: jpr Date: Tue, 4 May 2004 15:03:01 +0000 Subject: [PATCH] Taking into account the 'Dicom Sequences' leads up to introduce new concepts (and new classes) : a 'gdcmDocument' is composed of a set of Doc Entries, that are - elementary gdcmDocEntries (former gdcmHeaderEntries) - Sequence Doc Entries (gdcmSeqEntries) a Sequence is composed of Items. Each item is a set of Doc Entries (this is recursive) The non uniqueness of the 'Dicom tag' is due to this recursivity (never taken into account) Our unability to add a new 'entry' in the header at the proper location (neither in the H-Table (map or multimap), nor in the Chained List is also due to this recursivity. Don't try, right now, to use/modify/compile these new sources : nothing is finished. We just commit all the stuff, 'as is', in order not to loose it. --- src/gdcmBinEntry.cxx | 42 + src/gdcmBinEntry.h | 42 + src/gdcmDocEntry.cxx | 157 +++ src/gdcmDocEntry.h | 161 +++ src/gdcmDocEntrySet.cxx | 247 ++++ src/gdcmDocEntrySet.h | 25 + src/gdcmDocument.cxx | 2347 +++++++++++++++++++++++++++++++++++++++ src/gdcmDocument.h | 272 +++++ src/gdcmElementSet.cxx | 152 +++ src/gdcmElementSet.h | 21 + src/gdcmSQItem.cxx | 41 + src/gdcmSQItem.h | 40 + src/gdcmSeqEntry.cxx | 53 + src/gdcmSeqEntry.h | 43 + src/gdcmValEntry.cxx | 42 + src/gdcmValEntry.h | 40 + 16 files changed, 3725 insertions(+) create mode 100644 src/gdcmBinEntry.cxx create mode 100644 src/gdcmBinEntry.h create mode 100644 src/gdcmDocEntry.cxx create mode 100644 src/gdcmDocEntry.h create mode 100644 src/gdcmDocEntrySet.cxx create mode 100644 src/gdcmDocEntrySet.h create mode 100644 src/gdcmDocument.cxx create mode 100644 src/gdcmDocument.h create mode 100644 src/gdcmElementSet.cxx create mode 100644 src/gdcmElementSet.h create mode 100644 src/gdcmSQItem.cxx create mode 100644 src/gdcmSQItem.h create mode 100644 src/gdcmSeqEntry.cxx create mode 100644 src/gdcmSeqEntry.h create mode 100644 src/gdcmValEntry.cxx create mode 100644 src/gdcmValEntry.h diff --git a/src/gdcmBinEntry.cxx b/src/gdcmBinEntry.cxx new file mode 100644 index 00000000..9d09f7ad --- /dev/null +++ b/src/gdcmBinEntry.cxx @@ -0,0 +1,42 @@ +// gdcmBinEntry.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmBinEntry.h" +#include "gdcmTS.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" + + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmBinEntry + * \brief Constructor from a given gdcmBinEntry + * @param in Pointer to existing dictionary entry + */ +gdcmBinEntry::gdcmBinEntry() : gdcmDocEntry( ) { + + +} + + + + +//----------------------------------------------------------------------------- +// Print +/* + * \ingroup gdcmDocEntry + * \brief canonical Printer + */ + + +//----------------------------------------------------------------------------- +// Public + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +//----------------------------------------------------------------------------- diff --git a/src/gdcmBinEntry.h b/src/gdcmBinEntry.h new file mode 100644 index 00000000..76569f53 --- /dev/null +++ b/src/gdcmBinEntry.h @@ -0,0 +1,42 @@ +// gdcmBinEntry.h +//----------------------------------------------------------------------------- +#ifndef GDCMBinEntry_H +#define GDCMBinEntry_H + +#include +#include + +//----------------------------------------------------------------------------- +/** + * \ingroup gdcmBinEntry + * \brief The dicom header of a Dicom file contains a set of such entries + * (when successfuly parsed against a given Dicom dictionary) + * This one contains a 'string value'. + */ +class GDCM_EXPORT gdcmBinEntry : public gdcmValEntry { + +public: + +protected: + +private: + +// Variables + + + /// \brief unsecure memory area to hold 'non string' values + /// (ie : Lookup Tables, overlays, icons) + void *voidArea; + + + + + + + + +}; + +//----------------------------------------------------------------------------- +#endif + diff --git a/src/gdcmDocEntry.cxx b/src/gdcmDocEntry.cxx new file mode 100644 index 00000000..ffd36020 --- /dev/null +++ b/src/gdcmDocEntry.cxx @@ -0,0 +1,157 @@ +// gdcmDocEntry.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmDocEntry.h" +#include "gdcmTS.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" + +#include // for std::ios::left, ... + + +#define MAX_SIZE_PRINT_ELEMENT_VALUE 64 + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmDocEntry + * \brief Constructor from a given gdcmDictEntry + * @param in Pointer to existing dictionary entry + */ +gdcmDocEntry::gdcmDocEntry(gdcmDictEntry* in) { + ImplicitVR = false; + entry = in; +} + +//----------------------------------------------------------------------------- +// Print +/** + * \ingroup gdcmDocEntry + * \brief canonical Printer + */ +void gdcmDocEntry::Print(std::ostream & os) { + size_t o; + unsigned short int g, e; + char st[20]; + TSKey v; + std::string d2, vr; + gdcmTS * ts = gdcmGlobal::GetTS(); + std::ostringstream s; + guint32 lgth; + char greltag[10]; //group element tag + + g = GetGroup(); + e = GetElement(); + v = GetValue(); + o = GetOffset(); + vr = GetVR(); + sprintf(greltag,"%04x|%04x ",g,e); + s << greltag ; + + d2 = CreateCleanString(v); // replace non printable characters by '.' + if (printLevel>=2) { + s << "lg : "; + lgth = GetReadLength(); // ReadLength, as opposed to UsableLength + if (lgth == 0xffffffff) { + sprintf(st,"x(ffff)"); // I said : "x(ffff)" ! + s.setf(std::ios::left); + s << std::setw(10-strlen(st)) << " "; + s << st << " "; + s.setf(std::ios::left); + s << std::setw(8) << "-1"; + } else { + sprintf(st,"x(%x)",lgth); + s.setf(std::ios::left); + s << std::setw(10-strlen(st)) << " "; + s << st << " "; + s.setf(std::ios::left); + s << std::setw(8) << lgth; + } + s << " Off.: "; + sprintf(st,"x(%x)",o); + s << std::setw(10-strlen(st)) << " "; + s << st << " "; + s << std::setw(8) << o; + } + + s << "[" << vr << "] "; + + if (printLevel>=1) { + s.setf(std::ios::left); + s << std::setw(66-GetName().length()) << " "; + } + + s << "[" << GetName()<< "]"; + + if (voidArea != NULL) { + s << " [gdcm::Non String Data Loaded in Unsecure Area (" + << GetLength() << ") ]"; + } + + else { + if( (GetLength()=3) || + (d2.find("gdcm::NotLoaded.") < d2.length()) ) + s << " [" << d2 << "]"; + else + s << " [gdcm::too long for print (" << GetLength() << ") ]"; + } + + // Display the UID value (instead of displaying only the rough code) + if (g == 0x0002) { // Any more to be displayed ? + if ( (e == 0x0010) || (e == 0x0002) ) + s << " ==>\t[" << ts->GetValue(v) << "]"; + } else { + if (g == 0x0008) { + if ( (e == 0x0016) || (e == 0x1150) ) + s << " ==>\t[" << ts->GetValue(v) << "]"; + } else { + if (g == 0x0004) { + if ( (e == 0x1510) || (e == 0x1512) ) + s << " ==>\t[" << ts->GetValue(v) << "]"; + } + } + } + //if (e == 0x0000) { // elem 0x0000 --> group length + if ( (vr == "UL") || (vr == "US") || (vr == "SL") || (vr == "SS") ) { + if (v == "4294967295") // to avoid troubles in convertion + sprintf (st," x(ffffffff)"); + else { + if ( GetLength() !=0 ) + sprintf(st," x(%x)", atoi(v.c_str()));//FIXME + else + sprintf(st," "); + } + s << st; + } + s << std::endl; + os << s.str(); +} + +//----------------------------------------------------------------------------- +// Public + +/** + * \ingroup gdcmDocEntry + * \brief Gets the full length of the HeaderEntry (not only value length) + */ +guint32 gdcmDocEntry::GetFullLength(void) { + guint32 l; + l = GetLength(); + if ( IsImplicitVR() ) + l = l + 8; // 2 (gr) + 2 (el) + 4 (lgth) + else + if ( GetVR()=="OB" || GetVR()=="OW" || GetVR()=="SQ" ) + l = l + 12; // 2 (gr) + 2 (el) + 2 (vr) + 2 (unused) + 4 (lgth) + else + l = l + 8; // 2 (gr) + 2 (el) + 2 (vr) + 2 (lgth) + return(l); +} + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +//----------------------------------------------------------------------------- diff --git a/src/gdcmDocEntry.h b/src/gdcmDocEntry.h new file mode 100644 index 00000000..e412e5fd --- /dev/null +++ b/src/gdcmDocEntry.h @@ -0,0 +1,161 @@ +// gdcmDocEntry.h +//----------------------------------------------------------------------------- +#ifndef GDCMDocEntry_H +#define GDCMDocEntry_H + +#include +#include + +#include "gdcmDictEntry.h" +class gdcmHeader; + +//----------------------------------------------------------------------------- +/** + * \ingroup gdcmDocEntry + * \brief The dicom header of a Dicom file contains a set of such entries + * (when successfuly parsed against a given Dicom dictionary) + */ +class GDCM_EXPORT gdcmDocEntry { +public: + gdcmDocEntry(gdcmDictEntry*); + + /// Returns the Dicom Group number of the current Dicom Header Entry + inline guint16 GetGroup(void) { return entry->GetGroup(); }; + + /// Returns the Dicom Element number of the current Dicom Header Entry + inline guint16 GetElement(void) { return entry->GetElement();}; + + /// Returns the 'key' of the current Dicom Header Entry + inline std::string GetKey(void) { return entry->GetKey(); }; + + /// \brief Returns the 'Name' '(e.g. "Patient's Name") found in the Dicom + /// Dictionnary of the current Dicom Header Entry + inline std::string GetName(void) { return entry->GetName(); }; + + /// \brief Returns the 'Value Representation' (e.g. "PN" : Person Name, + /// "SL" : Signed Long), found in the Dicom Header or in the Dicom + /// Dictionnary, of the current Dicom Header Entry + inline std::string GetVR(void) { return entry->GetVR(); }; + + /// \brief Returns the 'Value' (e.g. "Dupond Marcel") converted into a + /// 'string', if it's stored as an integer in the Dicom Header of the + /// current Dicom Header Entry + inline std::string GetValue(void) { return value; }; + + /// \brief Returns the area value of the current Dicom Header Entry + /// when it's not string-translatable (e.g : a LUT table) + inline void * GetVoidArea(void) { return voidArea; }; + + /// \brief Returns offset (since the beginning of the file, including + /// the File Pramble, if any) of the value of the current Dicom HeaderEntry + /// \warning offset of the *value*, not of the Dicom Header Entry + inline size_t GetOffset(void) { return Offset; }; + + /// \brief Returns the actual value length of the current Dicom Header Entry + /// \warning this value is not *allways* the one stored in the Dicom Header + /// in case of well knowned bugs + inline guint32 GetLength(void) { return UsableLength; }; + + /// \brief Returns the 'read length' of the current Dicom Header Entry + /// \warning this value is the one stored in the Dicom Header but not + /// mandatoryly the one thats's used (in case on SQ, or delimiters, + /// the usable length is set to zero) + inline guint32 GetReadLength(void) { return ReadLength; }; + + /// Sets the 'Value Representation' of the current Dicom Header Entry + inline void SetVR(std::string v) { entry->SetVR(v); }; + + /// \brief Sets both 'Read Length' and 'Usable Length' of the current + /// Dicom Header Entry + inline void SetLength(guint32 l) { ReadLength=UsableLength=l;}; + + // The following 3 members, for internal use only ! + + /// \brief Sets only 'Read Length' (*not* 'Usable Length') of the current + /// Dicom Header Entry + inline void SetReadLength(guint32 l) { ReadLength = l; }; + + /// \brief Sets only 'Usable Length' (*not* 'Read Length') of the current + /// Dicom Header Entry + inline void SetUsableLength(guint32 l) { UsableLength = l; }; + + /// Sets the value (string) of the current Dicom Header Entry + inline void SetValue(std::string val) { value = val; }; + + /// Sets the value (non string) of the current Dicom Header Entry + inline void SetVoidArea(void * area) { voidArea = area; }; + + /// \brief Sets the offset of the Dicom Element + /// \warning use with caution ! + /// @param of offset to be set + inline void gdcmDocEntry::SetOffset(size_t of) { Offset = of; }; + + /// Sets to TRUE the ImplicitVr flag of the current Dicom Element + inline void gdcmDocEntry::SetImplicitVR(void) { ImplicitVR = true; }; + + /// \brief Tells us if the current Dicom Element was checked as ImplicitVr + /// @return true if the current Dicom Element was checked as ImplicitVr + inline bool gdcmDocEntry::IsImplicitVR(void) { return ImplicitVR; }; + + /// \brief Tells us if the VR of the current Dicom Element is Unkonwn + /// @return true if the VR is unkonwn + inline bool gdcmDocEntry::IsVRUnknown(void) + { return entry->IsVRUnknown(); }; + + /// \brief Sets the DicEntry of the current Dicom Element + /// @param NewEntry pointer to the DictEntry + inline void gdcmDocEntry::SetDictEntry(gdcmDictEntry *NewEntry) + { entry = NewEntry; }; + + /// \brief Gets the DicEntry of the current Dicom Element + /// @return The DicEntry of the current Dicom Element + gdcmDictEntry * gdcmDocEntry::GetDictEntry(void) { return entry; }; + + /// \brief Sets the print level for the Dicom Header Elements + /// \note 0 for Light Print; 1 for 'medium' Print, 2 for Heavy + void SetPrintLevel(int level) { printLevel = level; }; + + void Print (std::ostream & os = std::cout); + + /// Gets the depth level of a Dicom Header Entry embedded in a SeQuence + inline int GetSQDepthLevel(void) { return (SQDepthLevel); }; + + guint32 GetFullLength(void); + +private: + // FIXME: In fact we should be more specific and use : + // friend gdcmDocEntry * gdcmHeader::ReadNextElement(void); + friend class gdcmHeader; + + /// Sets the depth level of a Dicom Header Entry embedded in a SeQuence + inline void SetSQDepthLevel(int depthLevel) { SQDepthLevel = depthLevel; }; + +// Variables + gdcmDictEntry *entry; + + /// \brief Updated from ReadLength, by FixFoungLentgh() for fixing a bug + /// in the header or helping the parser going on + guint32 UsableLength; + + /// \brief Length actually read on disk (before FixFoundLength). ReadLength + /// will be updated only when FixFoundLength actually fixes a bug in the + /// header, not when it performs a trick to help the Parser going on. + guint32 ReadLength; + + /// \brief Even when reading explicit vr files, some elements happen to + /// be implicit. Flag them here since we can't use the entry->vr without + /// breaking the underlying dictionary. + bool ImplicitVR; + + /// Offset from the begining of file for direct user access + size_t Offset; + + /// How many details are to be printed (value : 0,1,2) + int printLevel; + + /// Gives the depth level of elements inside SeQuences + int SQDepthLevel; +}; + +//----------------------------------------------------------------------------- +#endif diff --git a/src/gdcmDocEntrySet.cxx b/src/gdcmDocEntrySet.cxx new file mode 100644 index 00000000..605df95a --- /dev/null +++ b/src/gdcmDocEntrySet.cxx @@ -0,0 +1,247 @@ +// gdcmDocEntrySet.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmDocEntrySet.h" +#include "gdcmTS.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" + +#include // for std::ios::left, ... + + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmDocEntrySEt + * \brief Constructor from a given gdcmDocEntrySet + */ +gdcmDocEntrySet::gdcmDocEntrySet() { + +} + +//----------------------------------------------------------------------------- +// Print +/* + * \ingroup gdcmDocEntrySet + * \brief canonical Printer + */ + + +//----------------------------------------------------------------------------- +// Public + + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +/** + * \brief Parses an EntrySet (Document header or SQ Item ) + * \ and load element values (a voir !) + * @return false if file is not ACR-NEMA / PAPYRUS / DICOM + */ +bool gdcmDocument::LoadDocEntrySet(bool exception_on_error) throw(gdcmFormatError) { + (void)exception_on_error; + rewind(fp); + if (!CheckSwap()) + return false; + + guint16 g, n; + gdcmValEntry *newValEntry = (gdcmValEntry *)0; + gdcmBinEntry *newBinEntry = (gdcmBinEntry *)0; + gdcmSeqEntry *newSeqEntry = (gdcmSeqEntry *)0; + gdcmDictEntry *NewTag = (gdcmDictEntry *)0; + char VR[3]; + long PositionOnEntry; + +// while ( (newHeaderEntry = ReadNextHeaderEntry()) ) { + while (1) { + + // ----------------------- was ReadNextHeaderEntry ----------------- + g = ReadInt16(); + n = ReadInt16(); + if (errno == 1) + // We reached the EOF (or an error occured) therefore + // header parsing has to be considered as finished. + break; + + // Find out if the tag we encountered is in the dictionaries: + DictEntry = GetDictEntryByNumber(Group, Elem); + if (!DictEntry) + DictEntry = NewVirtualDictEntry(Group, Elem); + if (!DictEntry) { + dbg.Verbose(1, "gdcmDocEntrySet::LoadDocEntrySet", + "failed to allocate gdcmDictEntry"); + return false; + } + + // Right now, + vr = DictEntry->GetVR(); // Suppose we get it, suppose it's the same one + // that's in the Explicit VR part of the Header + // TODO + // Resoudre pb quand inadequation entre Explicit VR et Dict + + if (filetype != ExplicitVR) {} // jamais de risque de conflit + // pour les autres cas, + // refaire l'equiv de gdcmParser::FindHeaderEntryVR + // qui contient CheckHeaderEntryVR + // le pb, c'est qu'on a besoin de la VR pour fabriquer l'Entry + + if (vr == "SQ" ) { + // --- SeqEntry + // TODO + // SEQUENCE; appel 'récursif' de ??? pour charger la 'valeur' + // (ensemble d' ITEMs, en fait, + // chaque ITEM etant chargé avec LoadDocEntrySet) + + + } else if (vr == "AE" || vr == "AS" || vr == "DA" || vr == "PN" || + vr == "UI" || vr == "TM" ) { + // --- ValEntry + NewValEntry = new gdcmValEntry(DictEntry); + if (!NewValEntry) { + dbg.Verbose(1, "gdcmDocEntrySet::LoadDocEntrySet", + "failed to allocate gdcmValEntry"); + return false; + } + FindHeaderEntryVR(NewEntry); + FindHeaderEntryLength(NewEntry); + } + + + else { + // --- BinEntry + NewBinEntry = new gdcmBinEntry(DictEntry); + if (!NewValEntry) { + dbg.Verbose(1, "gdcmDocEntrySet::LoadDocEntrySet", + "failed to allocate gdcmBinEntry"); + return false; + } + } + + + +// ------------- end of former ReadNextHeaderEntry ----------------- + + SkipHeaderEntry(newHeaderEntry); + if ( (ignoreShadow==0) || (newHeaderEntry->GetGroup()%2) == 0) { + AddHeaderEntry(newHeaderEntry); + } + } + rewind(fp); + // Be carefull : merging this two loops may cause troubles ... + for (ListTag::iterator i = GetListEntry().begin(); + i != GetListEntry().end(); + ++i) + { + LoadHeaderEntry(*i); + } + rewind(fp); + + + // -------------------------------------------------------------- + // Special Patch to allow gdcm to read ACR-LibIDO formated images + // + // if recognition code tells us we deal with a LibIDO image + // we switch lineNumber and columnNumber + // + std::string RecCode; + RecCode = GetEntryByNumber(0x0008, 0x0010); // recognition code + if (RecCode == "ACRNEMA_LIBIDO_1.1" || + RecCode == "CANRME_AILIBOD1_1." ) // for brain-damaged softwares + // with "little-endian strings" + { + filetype = ACR_LIBIDO; + std::string rows = GetEntryByNumber(0x0028, 0x0010); + std::string columns = GetEntryByNumber(0x0028, 0x0011); + SetEntryByNumber(columns, 0x0028, 0x0010); + SetEntryByNumber(rows , 0x0028, 0x0011); + } + // ----------------- End of Special Patch ---------------- + return true; +} + + + +/** + * \brief Check the correspondance between the VR of the header entry + * and the taken VR. If they are different, the header entry is + * updated with the new VR. + * @param Entry Header Entry to check + * @param vr Dicom Value Representation + * @return false if the VR is incorrect of if the VR isn't referenced + * otherwise, it returns true +*/ + +// NE MARCHE PAS EN L'ETAT : +// On a besoin de VR pour 'fabriquer', au choix ValEntry, BinEntry, ou SeqEntry. +// + +bool gdcmDocEntrySet::CheckEntryVR(gdcmHeaderEntry *Entry, VRKey vr) +{ + char msg[100]; // for sprintf + bool RealExplicit = true; + + // Assume we are reading a falsely explicit VR file i.e. we reached + // a tag where we expect reading a VR but are in fact we read the + // first to bytes of the length. Then we will interogate (through find) + // the dicom_vr dictionary with oddities like "\004\0" which crashes + // both GCC and VC++ implementations of the STL map. Hence when the + // expected VR read happens to be non-ascii characters we consider + // we hit falsely explicit VR tag. + + if ( (!isalpha(vr[0])) && (!isalpha(vr[1])) ) + RealExplicit = false; + + // CLEANME searching the dicom_vr at each occurence is expensive. + // PostPone this test in an optional integrity check at the end + // of parsing or only in debug mode. + if ( RealExplicit && !gdcmGlobal::GetVR()->Count(vr) ) + RealExplicit= false; + + if ( !RealExplicit ) + { + // We thought this was explicit VR, but we end up with an + // implicit VR tag. Let's backtrack. + sprintf(msg,"Falsely explicit vr file (%04x,%04x)\n", + Entry->GetGroup(),Entry->GetElement()); + dbg.Verbose(1, "gdcmParser::FindVR: ",msg); + if (Entry->GetGroup()%2 && Entry->GetElement() == 0x0000) { // Group length is UL ! + gdcmDictEntry* NewEntry = NewVirtualDictEntry( + Entry->GetGroup(),Entry->GetElement(), + "UL","FIXME","Group Length"); + Entry->SetDictEntry(NewEntry); + } + return(false); + } + + if ( Entry->IsVRUnknown() ) + { + // When not a dictionary entry, we can safely overwrite the VR. + if (Entry->GetElement() == 0x0000) { // Group length is UL ! + Entry->SetVR("UL"); + } else { + Entry->SetVR(vr); + } + } + else if ( Entry->GetVR() != vr ) + { + // The VR present in the file and the dictionary disagree. We assume + // the file writer knew best and use the VR of the file. Since it would + // be unwise to overwrite the VR of a dictionary (since it would + // compromise it's next user), we need to clone the actual DictEntry + // and change the VR for the read one. + gdcmDictEntry* NewEntry = NewVirtualDictEntry( + Entry->GetGroup(),Entry->GetElement(), + vr,"FIXME",Entry->GetName()); + Entry->SetDictEntry(NewEntry); + } + return(true); +} + + + + +//----------------------------------------------------------------------------- diff --git a/src/gdcmDocEntrySet.h b/src/gdcmDocEntrySet.h new file mode 100644 index 00000000..78375a3c --- /dev/null +++ b/src/gdcmDocEntrySet.h @@ -0,0 +1,25 @@ +// gdcmDocEntrySet.h + +#ifndef GDCMDOCENTRYSET_H +#define GDCMDOCENTRYSET_H + +//----------------------------------------------------------------------------- + +class GDCM_EXPORT gdcmDocEntrySet +{ +public: + +void gdcmElementSet::FindDocEntryLength (gdcmDocEntry *Entry); + +protected: + +private: + +bool gdcmDocEntrySet::LoadDocEntriesSet(bool exception_on_error = false) + throw(gdcmFormatError); +}; + + +//----------------------------------------------------------------------------- +#endif + diff --git a/src/gdcmDocument.cxx b/src/gdcmDocument.cxx new file mode 100644 index 00000000..4af6b8f0 --- /dev/null +++ b/src/gdcmDocument.cxx @@ -0,0 +1,2347 @@ +// gdcmDocument.cxx +//----------------------------------------------------------------------------- + +#include "gdcmDocument.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" +#include "gdcmDebug.h" + +#include +#include + +// For nthos: +#ifdef _MSC_VER + #include +#else + #include +#endif + +# include + +#define UI1_2_840_10008_1_2 "1.2.840.10008.1.2" +#define UI1_2_840_10008_1_2_1 "1.2.840.10008.1.2.1" +#define UI1_2_840_10008_1_2_2 "1.2.840.10008.1.2.2" +#define UI1_2_840_10008_1_2_1_99 "1.2.840.10008.1.2.1.99" + +typedef struct { + guint32 totalSQlength; + guint32 alreadyParsedlength; +} pileElem; + +//----------------------------------------------------------------------------- +// Refer to gdcmDocument::CheckSwap() +const unsigned int gdcmDocument::HEADER_LENGTH_TO_READ = 256; + +// Refer to gdcmDocument::SetMaxSizeLoadEntry() +const unsigned int gdcmDocument::MAX_SIZE_LOAD_ELEMENT_VALUE = 4096; + +const unsigned int gdcmDocument::MAX_SIZE_PRINT_ELEMENT_VALUE = 64; + +//----------------------------------------------------------------------------- +// Constructor / Destructor + +/** + * \brief constructor + * @param inFilename file to be opened for parsing + * @param exception_on_error whether we throw an exception or not + * @param enable_sequences = true to allow the header + * to be parsed *inside* the SeQuences, + * when they have an actual length + * \warning enable_sequences *has to be* true for reading PAPYRUS 3.0 files + * @param ignore_shadow to allow skipping the shadow elements, + * to save memory space. + * \warning The TRUE value for this param has to be used + * with a FALSE value for the 'enable_sequence' param. + * ('public elements' may be embedded in 'shadow Sequences') + */ +gdcmDocument::gdcmDocument(const char *inFilename, + bool exception_on_error, + bool enable_sequences, + bool ignore_shadow) { + enableSequences=enable_sequences; + ignoreShadow =ignore_shadow; + + SetMaxSizeLoadEntry(MAX_SIZE_LOAD_ELEMENT_VALUE); + filename = inFilename; + Initialise(); + + if ( !OpenFile(exception_on_error)) + return; + + LoadDocEntries(); + CloseFile(); + + wasUpdated = 0; // will be set to 1 if user adds an entry + printLevel = 1; // 'Medium' print level by default +} + +/** + * \brief constructor + * @param exception_on_error + */ +gdcmDocument::gdcmDocument(bool exception_on_error) { + (void)exception_on_error; + enableSequences=0; + + SetMaxSizeLoadEntry(MAX_SIZE_LOAD_ELEMENT_VALUE); + Initialise(); + + wasUpdated = 0; // will be set to 1 if user adds an entry + printLevel = 1; // 'Medium' print level by default +} + +/** + * \brief Canonical destructor. + */ +gdcmDocument::~gdcmDocument (void) { + RefPubDict = NULL; + RefShaDict = NULL; +} + +//----------------------------------------------------------------------------- +// Print +/** + * \brief Prints the Doc Entries (Dicom Elements) + * from the chained list + * @return + */ +void gdcmDocument::PrintEntry(std::ostream & os) { + + for (ListTag::iterator i = listEntries.begin(); + i != listEntries.end(); + ++i) + { + (*i)->SetPrintLevel(printLevel); + (*i)->Print(os); + } +} + + +/** + * \brief Prints The Dict Entries of THE public Dicom Dictionary + * @return + */ +void gdcmDocument::PrintPubDict(std::ostream & os) { + RefPubDict->Print(os); +} + +/** + * \brief Prints The Dict Entries of THE shadow Dicom Dictionary + * @return + */ +void gdcmDocument::PrintShaDict(std::ostream & os) { + RefShaDict->Print(os); +} + +//----------------------------------------------------------------------------- +// Public +/** + * \brief Get the public dictionary used + */ +gdcmDict *gdcmDocument::GetPubDict(void) { + return(RefPubDict); +} + +/** + * \brief Get the shadow dictionary used + */ +gdcmDict *gdcmDocument::GetShaDict(void) { + return(RefShaDict); +} + +/** + * \brief Set the shadow dictionary used + * \param dict dictionary to use in shadow + */ +bool gdcmDocument::SetShaDict(gdcmDict *dict){ + RefShaDict=dict; + return(!RefShaDict); +} + +/** + * \brief Set the shadow dictionary used + * \param dictName name of the dictionary to use in shadow + */ +bool gdcmDocument::SetShaDict(DictKey dictName){ + RefShaDict=gdcmGlobal::GetDicts()->GetDict(dictName); + return(!RefShaDict); +} + +/** + * \brief This predicate, based on hopefully reasonable heuristics, + * decides whether or not the current gdcmDocument was properly parsed + * and contains the mandatory information for being considered as + * a well formed and usable Dicom/Acr File. + * @return true when gdcmDocument is the one of a reasonable Dicom/Acr file, + * false otherwise. + */ +bool gdcmDocument::IsReadable(void) { + if(filetype==Unknown) { + return(false); + } + if(listEntries.size()<=0) { + return(false); + } + + return(true); +} + +/** + * \brief Determines if the Transfer Syntax was already encountered + * and if it corresponds to a ImplicitVRLittleEndian one. + * @return True when ImplicitVRLittleEndian found. False in all other cases. + */ +bool gdcmDocument::IsImplicitVRLittleEndianTransferSyntax(void) { + gdcmHeaderEntry *Element = GetHeaderEntryByNumber(0x0002, 0x0010); + if ( !Element ) + return false; + LoadHeaderEntrySafe(Element); + + std::string Transfer = Element->GetValue(); + if ( Transfer == UI1_2_840_10008_1_2 ) + return true; + return false; +} + +/** + * \brief Determines if the Transfer Syntax was already encountered + * and if it corresponds to a ExplicitVRLittleEndian one. + * @return True when ExplicitVRLittleEndian found. False in all other cases. + */ +bool gdcmDocument::IsExplicitVRLittleEndianTransferSyntax(void) { + gdcmHeaderEntry* Element = GetHeaderEntryByNumber(0x0002, 0x0010); + if ( !Element ) + return false; + LoadHeaderEntrySafe(Element); + + std::string Transfer = Element->GetValue(); + if ( Transfer == UI1_2_840_10008_1_2_1 ) + return true; + return false; +} + +/** + * \brief Determines if the Transfer Syntax was already encountered + * and if it corresponds to a DeflatedExplicitVRLittleEndian one. + * @return True when DeflatedExplicitVRLittleEndian found. False in all other cases. + */ +bool gdcmDocument::IsDeflatedExplicitVRLittleEndianTransferSyntax(void) { + gdcmHeaderEntry* Element = GetHeaderEntryByNumber(0x0002, 0x0010); + if ( !Element ) + return false; + LoadHeaderEntrySafe(Element); + + std::string Transfer = Element->GetValue(); + if ( Transfer == UI1_2_840_10008_1_2_1_99 ) + return true; + return false; +} + +/** + * \brief Determines if the Transfer Syntax was already encountered + * and if it corresponds to a Explicit VR Big Endian one. + * @return True when big endian found. False in all other cases. + */ +bool gdcmDocument::IsExplicitVRBigEndianTransferSyntax(void) { + gdcmHeaderEntry* Element = GetHeaderEntryByNumber(0x0002, 0x0010); + if ( !Element ) + return false; + LoadHeaderEntrySafe(Element); + + std::string Transfer = Element->GetValue(); + if ( Transfer == UI1_2_840_10008_1_2_2 ) //1.2.2 ??? A verifier ! + return true; + return false; +} + +/** + * \brief returns the File Type + * (ACR, ACR_LIBIDO, ExplicitVR, ImplicitVR, Unknown) + * @return the FileType code + */ +FileType gdcmDocument::GetFileType(void) { + return(filetype); +} + +/** + * \brief opens the file + * @param exception_on_error + * @return + */ +FILE *gdcmDocument::OpenFile(bool exception_on_error) + throw(gdcmFileError) +{ + fp=fopen(filename.c_str(),"rb"); + if(exception_on_error) + { + if(!fp) + throw gdcmFileError("gdcmDocument::gdcmDocument(const char *, bool)"); + } + + if ( fp ) + { + guint16 zero; + fread(&zero, (size_t)2, (size_t)1, fp); + + //ACR -- or DICOM with no Preamble -- + if( zero == 0x0008 || zero == 0x0800 || zero == 0x0002 || zero == 0x0200) + return(fp); + + //DICOM + fseek(fp, 126L, SEEK_CUR); + char dicm[4]; + fread(dicm, (size_t)4, (size_t)1, fp); + if( memcmp(dicm, "DICM", 4) == 0 ) + return(fp); + + fclose(fp); + dbg.Verbose(0, "gdcmDocument::OpenFile not DICOM/ACR", filename.c_str()); + } + else { + dbg.Verbose(0, "gdcmDocument::OpenFile cannot open file", filename.c_str()); + } + return(NULL); +} + +/** + * \brief closes the file + * @return TRUE if the close was successfull + */ +bool gdcmDocument::CloseFile(void) { + int closed = fclose(fp); + fp = (FILE *)0; + if (! closed) + return false; + return true; +} + +/** + * \brief Writes in a file all the Doc Entries (Dicom Elements) + * of the Chained List + * @param fp file pointer on an already open file + * @param type Type of the File to be written + * (ACR-NEMA, ExplicitVR, ImplicitVR) + * \return Always true. + */ +bool gdcmDocument::Write(FILE *fp, FileType type) { +/// \todo +/// ============== +/// The stuff will have to be rewritten using the SeQuence based +/// tree-like stucture instead of the chained list . +/// (so we shall remove the GroupHT from the gdcmDocument) +/// To be checked +/// ============= + + /// \todo move the following lines (and a lot of others, to be written) + /// to a future function CheckAndCorrectHeader + + /// \todo + /// Question : + /// Comment pourrait-on savoir si le DcmHeader vient d'un fichier + /// DicomV3 ou non (FileType est un champ de gdcmDocument ...) + /// WARNING : Si on veut ecrire du DICOM V3 a partir d'un DcmHeader ACR-NEMA + /// no way + /// a moins de se livrer a un tres complique ajout des champs manquants. + /// faire un CheckAndCorrectHeader (?) + + if (type == ImplicitVR) + { + std::string implicitVRTransfertSyntax = UI1_2_840_10008_1_2; + ReplaceOrCreateByNumber(implicitVRTransfertSyntax,0x0002, 0x0010); + + /// \todo Refer to standards on page 21, chapter 6.2 + /// "Value representation": values with a VR of UI shall be + /// padded with a single trailing null + /// Dans le cas suivant on doit pader manuellement avec un 0 + + SetEntryLengthByNumber(18, 0x0002, 0x0010); + } + + if (type == ExplicitVR) + { + std::string explicitVRTransfertSyntax = UI1_2_840_10008_1_2_1; + ReplaceOrCreateByNumber(explicitVRTransfertSyntax,0x0002, 0x0010); + + /// \todo Refer to standards on page 21, chapter 6.2 + /// "Value representation": values with a VR of UI shall be + /// padded with a single trailing null + /// Dans le cas suivant on doit pader manuellement avec un 0 + + SetEntryLengthByNumber(20, 0x0002, 0x0010); + } + +/** + * \todo rewrite later, if really usefull + * + * --> Warning : un-updated odd groups lengths can causes pb + * --> (xmedcon breaks) + * --> to be re- written with future org. + * + * if ( (type == ImplicitVR) || (type == ExplicitVR) ) + * UpdateGroupLength(false,type); + * if ( type == ACR) + * UpdateGroupLength(true,ACR); + */ + + WriteEntries(fp,type); + return(true); +} + +/** + * \brief Modifies the value of a given Header Entry (Dicom Element) + * when it exists. Create it with the given value when unexistant. + * \warning Adds the Header Entry to the HTable, NOT to the chained List + * @param Value Value to be set + * @param Group Group of the Entry + * @param Elem Element of the Entry + * \return pointer to the modified/created Header Entry (NULL when creation + * failed). + */ +gdcmHeaderEntry * gdcmDocument::ReplaceOrCreateByNumber( + std::string Value, + guint16 Group, + guint16 Elem ){ + gdcmHeaderEntry* a; + a = GetHeaderEntryByNumber( Group, Elem); + if (a == NULL) { + a =NewHeaderEntryByNumber(Group, Elem); + if (a == NULL) + return NULL; + AddHeaderEntry(a); + } + //CLEANME SetEntryByNumber(Value, Group, Elem); + a->SetValue(Value); + return(a); +} + +/** + * \brief Set a new value if the invoked element exists + * Seems to be useless !!! + * @param Value new element value + * @param Group group of the Entry + * @param Elem element of the Entry + * \return boolean + */ +bool gdcmDocument::ReplaceIfExistByNumber(char* Value, guint16 Group, guint16 Elem ) +{ + std::string v = Value; + SetEntryByNumber(v, Group, Elem); + return true; +} + +//----------------------------------------------------------------------------- +// Protected + +/** + * \brief Checks if a given Dicom Element exists + * within the H table + * @param group Group number of the searched Dicom Element + * @param element Element number of the searched Dicom Element + * @return number of occurences + */ +int gdcmDocument::CheckIfEntryExistByNumber(guint16 group, guint16 element ) { + std::string key = gdcmDictEntry::TranslateToKey(group, element ); + return (tagHT.count(key)); +} + +/** + * \brief Searches within Doc Entries (Dicom Elements) parsed with + * the public and private dictionaries + * for the element value of a given tag. + * \warning Don't use any longer : use GetPubEntryByName + * @param tagName name of the searched element. + * @return Corresponding element value when it exists, + * and the string GDCM_UNFOUND ("gdcm::Unfound") otherwise. + */ +std::string gdcmDocument::GetEntryByName(std::string tagName) { + gdcmDictEntry *dictEntry = RefPubDict->GetDictEntryByName(tagName); + if( dictEntry == NULL) + return GDCM_UNFOUND; + + return(GetEntryByNumber(dictEntry->GetGroup(),dictEntry->GetElement())); +} + +/** + * \brief Searches within Doc Entries (Dicom Elements) parsed with + * the public and private dictionaries + * for the element value representation of a given tag. + * + * Obtaining the VR (Value Representation) might be needed by caller + * to convert the string typed content to caller's native type + * (think of C++ vs Python). The VR is actually of a higher level + * of semantics than just the native C++ type. + * @param tagName name of the searched element. + * @return Corresponding element value representation when it exists, + * and the string GDCM_UNFOUND ("gdcm::Unfound") otherwise. + */ +std::string gdcmDocument::GetEntryVRByName(std::string tagName) { + gdcmDictEntry *dictEntry = RefPubDict->GetDictEntryByName(tagName); + if( dictEntry == NULL) + return GDCM_UNFOUND; + + gdcmHeaderEntry* elem = GetHeaderEntryByNumber(dictEntry->GetGroup(), + dictEntry->GetElement()); + return elem->GetVR(); +} + + +/** + * \brief Searches within Doc Entries (Dicom Elements) parsed with + * the public and private dictionaries + * for the element value representation of a given tag. + * @param group Group of the searched tag. + * @param element Element of the searched tag. + * @return Corresponding element value representation when it exists, + * and the string GDCM_UNFOUND ("gdcm::Unfound") otherwise. + */ +std::string gdcmDocument::GetEntryByNumber(guint16 group, guint16 element){ + TagKey key = gdcmDictEntry::TranslateToKey(group, element); + if ( ! tagHT.count(key)) + return GDCM_UNFOUND; + return tagHT.find(key)->second->GetValue(); +} + +/** + * \brief Searches within Doc Entries (Dicom Elements) parsed with + * the public and private dictionaries + * for the element value representation of a given tag.. + * + * Obtaining the VR (Value Representation) might be needed by caller + * to convert the string typed content to caller's native type + * (think of C++ vs Python). The VR is actually of a higher level + * of semantics than just the native C++ type. + * @param group Group of the searched tag. + * @param element Element of the searched tag. + * @return Corresponding element value representation when it exists, + * and the string GDCM_UNFOUND ("gdcm::Unfound") otherwise. + */ +std::string gdcmDocument::GetEntryVRByNumber(guint16 group, guint16 element) { + gdcmHeaderEntry* elem = GetHeaderEntryByNumber(group, element); + if ( !elem ) + return GDCM_UNFOUND; + return elem->GetVR(); +} + +/** + * \brief Searches within Doc Entries (Dicom Elements) parsed with + * the public and private dictionaries + * for the value length of a given tag.. + * @param group Group of the searched tag. + * @param element Element of the searched tag. + * @return Corresponding element length; -2 if not found + */ +int gdcmDocument::GetEntryLengthByNumber(guint16 group, guint16 element) { + gdcmHeaderEntry* elem = GetHeaderEntryByNumber(group, element); + if ( !elem ) + return -2; + return elem->GetLength(); +} +/** + * \brief Sets the value (string) of the Header Entry (Dicom Element) + * @param content string value of the Dicom Element + * @param tagName name of the searched Dicom Element. + * @return true when found + */ +bool gdcmDocument::SetEntryByName(std::string content,std::string tagName) { + gdcmDictEntry *dictEntry = RefPubDict->GetDictEntryByName(tagName); + if( dictEntry == NULL) + return false; + + return(SetEntryByNumber(content,dictEntry->GetGroup(), + dictEntry->GetElement())); +} + +/** + * \brief Accesses an existing gdcmHeaderEntry (i.e. a Dicom Element) + * through it's (group, element) and modifies it's content with + * the given value. + * \warning Don't use any longer : use SetPubEntryByNumber + * @param content new value to substitute with + * @param group group of the Dicom Element to modify + * @param element element of the Dicom Element to modify + */ +bool gdcmDocument::SetEntryByNumber(std::string content, + guint16 group, + guint16 element) +{ + TagKey key = gdcmDictEntry::TranslateToKey(group, element); + if ( ! tagHT.count(key)) + return false; + int l = content.length(); + if(l%2) // Non even length are padded with a space (020H). + { + l++; + content = content + '\0'; + } + + gdcmHeaderEntry * a; + IterHT p; + TagHeaderEntryHT::iterator p2; + // DO NOT remove the following lines : they explain the stuff + //p= tagHT.equal_range(key); // get a pair of iterators first-last synonym + //p2=p.first; // iterator on the first synonym + //a=p2->second; // H Table target column (2-nd col) + + // or, easier : + a = ((tagHT.equal_range(key)).first)->second; + + a-> SetValue(content); + + std::string vr = a->GetVR(); + + guint32 lgr; + if( (vr == "US") || (vr == "SS") ) + lgr = 2; + else if( (vr == "UL") || (vr == "SL") ) + lgr = 4; + else + lgr = l; + + a->SetLength(lgr); + return true; +} + +/** + * \brief Accesses an existing gdcmHeaderEntry (i.e. a Dicom Element) + * in the PubHeaderEntrySet of this instance + * through it's (group, element) and modifies it's length with + * the given value. + * \warning Use with extreme caution. + * @param l new length to substitute with + * @param group group of the Entry to modify + * @param element element of the Entry to modify + * @return true on success, false otherwise. + */ +bool gdcmDocument::SetEntryLengthByNumber(guint32 l, + guint16 group, + guint16 element) +{ + TagKey key = gdcmDictEntry::TranslateToKey(group, element); + if ( ! tagHT.count(key)) + return false; + if (l%2) l++; // length must be even + ( ((tagHT.equal_range(key)).first)->second )->SetLength(l); + + return true ; +} + +/** + * \brief Gets (from Header) the offset of a 'non string' element value + * (LoadElementValues has already be executed) + * @param Group group of the Entry + * @param Elem element of the Entry + * @return File Offset of the Element Value + */ +size_t gdcmDocument::GetEntryOffsetByNumber(guint16 Group, guint16 Elem) +{ + gdcmHeaderEntry* Entry = GetHeaderEntryByNumber(Group, Elem); + if (!Entry) + { + dbg.Verbose(1, "gdcmDocument::GetHeaderEntryByNumber", + "failed to Locate gdcmHeaderEntry"); + return (size_t)0; + } + return Entry->GetOffset(); +} + +/** + * \brief Gets (from Header) a 'non string' element value + * (LoadElementValues has already be executed) + * @param Group group of the Entry + * @param Elem element of the Entry + * @return Pointer to the 'non string' area + */ +void * gdcmDocument::GetEntryVoidAreaByNumber(guint16 Group, guint16 Elem) +{ + gdcmHeaderEntry* Entry = GetHeaderEntryByNumber(Group, Elem); + if (!Entry) + { + dbg.Verbose(1, "gdcmDocument::GetHeaderEntryByNumber", + "failed to Locate gdcmHeaderEntry"); + return (NULL); + } + return Entry->GetVoidArea(); +} + +/** + * \brief Loads (from disk) the element content + * when a string is not suitable + * @param Group group of the Entry + * @param Elem element of the Entry + */ +void *gdcmDocument::LoadEntryVoidArea(guint16 Group, guint16 Elem) +{ + gdcmHeaderEntry * Element= GetHeaderEntryByNumber(Group, Elem); + if ( !Element ) + return NULL; + size_t o =(size_t)Element->GetOffset(); + fseek(fp, o, SEEK_SET); + size_t l=Element->GetLength(); + char* a = new char[l]; + if(!a) + return NULL; + + SetEntryVoidAreaByNumber(a, Group, Elem); + /// \todo check the result + size_t l2 = fread(a, 1, l ,fp); + if(l != l2) + { + delete[] a; + return NULL; + } + + return a; +} + +/** + * \brief Sets a 'non string' value to a given Dicom Element + * @param area + * @param group Group number of the searched Dicom Element + * @param element Element number of the searched Dicom Element + * @return + */ +bool gdcmDocument::SetEntryVoidAreaByNumber(void * area, + guint16 group, + guint16 element) +{ + TagKey key = gdcmDictEntry::TranslateToKey(group, element); + if ( ! tagHT.count(key)) + return false; + ( ((tagHT.equal_range(key)).first)->second )->SetVoidArea(area); + return true; +} + +/** + * \brief Update the entries with the shadow dictionary. + * Only non even entries are analyzed + */ +void gdcmDocument::UpdateShaEntries(void) { + gdcmDictEntry *entry; + std::string vr; + + for(ListTag::iterator it=listEntries.begin(); + it!=listEntries.end(); + ++it) + { + // Odd group => from public dictionary + if((*it)->GetGroup()%2==0) + continue; + + // Peer group => search the corresponding dict entry + if(RefShaDict) + entry=RefShaDict->GetDictEntryByNumber((*it)->GetGroup(),(*it)->GetElement()); + else + entry=NULL; + + if((*it)->IsImplicitVR()) + vr="Implicit"; + else + vr=(*it)->GetVR(); + + (*it)->SetValue(GetHeaderEntryUnvalue(*it)); + if(entry){ + // Set the new entry and the new value + (*it)->SetDictEntry(entry); + CheckHeaderEntryVR(*it,vr); + + (*it)->SetValue(GetHeaderEntryValue(*it)); + } + else + { + // Remove precedent value transformation + (*it)->SetDictEntry(NewVirtualDictEntry((*it)->GetGroup(),(*it)->GetElement(),vr)); + } + } +} + +/** + * \brief Searches within the Doc Entries for a Dicom Element of + * a given tag. + * @param tagName name of the searched Dicom Element. + * @return Corresponding Dicom Element when it exists, and NULL + * otherwise. + */ + gdcmHeaderEntry *gdcmDocument::GetHeaderEntryByName(std::string tagName) { + gdcmDictEntry *dictEntry = RefPubDict->GetDictEntryByName(tagName); + if( dictEntry == NULL) + return NULL; + + return(GetHeaderEntryByNumber(dictEntry->GetGroup(),dictEntry->GetElement())); +} + +/** + * \brief retrieves a Dicom Element (the first one) using (group, element) + * \warning (group, element) IS NOT an identifier inside the Dicom Header + * if you think it's NOT UNIQUE, check the count number + * and use iterators to retrieve ALL the Dicoms Elements within + * a given couple (group, element) + * @param group Group number of the searched Dicom Element + * @param element Element number of the searched Dicom Element + * @return + */ +gdcmHeaderEntry* gdcmDocument::GetHeaderEntryByNumber(guint16 group, guint16 element) +{ + TagKey key = gdcmDictEntry::TranslateToKey(group, element); + if ( ! tagHT.count(key)) + return NULL; + return tagHT.find(key)->second; +} + +/** + * \brief retrieves the Dicom Elements (all of them) using (group, element) + * @param group Group number of the searched Dicom Element. + * @param element Element number of the searched Dicom Element. + * @return a range (i.e.pair<,>) containing all elements whose key is group|element) + */ + +IterHT gdcmDocument::GetHeaderEntrySameNumber(guint16 group, guint16 element){ + TagKey key = gdcmDictEntry::TranslateToKey(group, element); + return (tagHT.equal_range(key)); +} + +/** + * \brief Loads the element while preserving the current + * underlying file position indicator as opposed to + * to LoadHeaderEntry that modifies it. + * @param entry Header Entry whose value shall be loaded. + * @return + */ +void gdcmDocument::LoadHeaderEntrySafe(gdcmHeaderEntry * entry) { + long PositionOnEntry = ftell(fp); + LoadHeaderEntry(entry); + fseek(fp, PositionOnEntry, SEEK_SET); +} + +/** + * \brief Re-computes the length of a ACR-NEMA/Dicom group from a DcmHeader + * \warning : to be re-written using the chained list instead of the H table. + * \warning : DO NOT use (doesn't work any longer because of the multimap) + * \todo : to be re-written using the chained list instead of the H table + * @param SkipSequence TRUE if we don't want to write Sequences (ACR-NEMA Files) + * @param type Type of the File (ExplicitVR,ImplicitVR, ACR, ...) + */ +void gdcmDocument::UpdateGroupLength(bool SkipSequence, FileType type) { + guint16 gr, el; + std::string vr; + + gdcmHeaderEntry *elem; + char trash[10]; + std::string str_trash; + + GroupKey key; + GroupHT groupHt; // to hold the length of each group + TagKey tk; + // remember : + // typedef std::map GroupHT; + + gdcmHeaderEntry *elemZ; + + // for each Tag in the DCM Header + + for (TagHeaderEntryHT::iterator tag2 = tagHT.begin(); + tag2 != tagHT.end(); + ++tag2) + { + elem = tag2->second; + gr = elem->GetGroup(); + el = elem->GetElement(); + vr = elem->GetVR(); + + sprintf(trash, "%04x", gr); + key = trash; // generate 'group tag' + + // if the caller decided not to take SEQUENCEs into account + // e.g : he wants to write an ACR-NEMA File + + if (SkipSequence && vr == "SQ") + continue; + + // Still unsolved problem : + // we cannot find the 'Sequence Delimitation Item' + // since it's at the end of the Hash Table + // (fffe,e0dd) + + // there is SEQUENCE in ACR-NEMA + // WARNING : + // --> la descente a l'interieur' des SQ + // devrait etre faite avec une liste chainee, pas avec une HTable... + + if ( groupHt.count(key) == 0) // we just read the first elem of a given group + { + if (el == 0x0000) // the first elem is 0x0000 + { + groupHt[key] = 0; // initialize group length + } + else + { + groupHt[key] = 2 + 2 + 4 + elem->GetLength(); // non 0x0000 first group elem + } + } + else // any elem but the first + { + if (type == ExplicitVR) + { + if ( (vr == "OB") || (vr == "OW") || (vr == "SQ") ) + { + groupHt[key] += 4; // explicit VR AND OB, OW, SQ : 4 more bytes + } + } + groupHt[key] += 2 + 2 + 4 + elem->GetLength(); + } + } + + unsigned short int gr_bid; + + for (GroupHT::iterator g = groupHt.begin(); // for each group we found + g != groupHt.end(); + ++g) + { + // FIXME: g++ -Wall -Wstrict-prototypes reports on following line: + // warning: unsigned int format, different type arg + sscanf(g->first.c_str(),"%x",&gr_bid); //FIXME + tk = g->first + "|0000"; // generate the element full tag + + if ( tagHT.count(tk) == 0) // if element 0x0000 not found + { + gdcmDictEntry * tagZ = new gdcmDictEntry(gr_bid, 0x0000, "UL"); + elemZ = new gdcmHeaderEntry(tagZ); + elemZ->SetLength(4); + AddHeaderEntry(elemZ); // create it + } + else + { + elemZ=GetHeaderEntryByNumber(gr_bid, 0x0000); + } + sprintf(trash ,"%d",g->second); + str_trash=trash; + elemZ->SetValue(str_trash); + } +} + +/** + * \brief Writes in a file (according to the requested format) + * the group, the element, the value representation and the length + * of a single gdcmHeaderEntry passed as argument. + * @param tag pointer on the gdcmHeaderEntry to be written + * @param _fp already open file pointer + * @param type type of the File to be written + */ +void gdcmDocument::WriteEntryTagVRLength(gdcmHeaderEntry *tag, + FILE *_fp, + FileType type) +{ + guint16 group = tag->GetGroup(); + std::string vr = tag->GetVR(); + guint16 el = tag->GetElement(); + guint32 lgr = tag->GetReadLength(); + + if ( (group == 0xfffe) && (el == 0x0000) ) + // Fix in order to make some MR PHILIPS images e-film readable + // see gdcmData/gdcm-MR-PHILIPS-16-Multi-Seq.dcm: + // we just *always* ignore spurious fffe|0000 tag ! + return; + + fwrite ( &group,(size_t)2 ,(size_t)1 ,_fp); //group + fwrite ( &el,(size_t)2 ,(size_t)1 ,_fp); //element + + if ( type == ExplicitVR ) { + + // Special case of delimiters: + if (group == 0xfffe) { + // Delimiters have NO Value Representation and have NO length. + // Hence we skip writing the VR and length and we pad by writing + // 0xffffffff + + int ff=0xffffffff; + fwrite (&ff,(size_t)4 ,(size_t)1 ,_fp); + return; + } + + guint16 z=0; + guint16 shortLgr = lgr; + if (vr == "unkn") { // Unknown was 'written' + // deal with Little Endian + fwrite ( &shortLgr,(size_t)2 ,(size_t)1 ,_fp); + fwrite ( &z, (size_t)2 ,(size_t)1 ,_fp); + } else { + fwrite (vr.c_str(),(size_t)2 ,(size_t)1 ,_fp); + if ( (vr == "OB") || (vr == "OW") || (vr == "SQ") ) + { + fwrite ( &z, (size_t)2 ,(size_t)1 ,_fp); + fwrite ( &lgr,(size_t)4 ,(size_t)1 ,_fp); + } else { + fwrite ( &shortLgr,(size_t)2 ,(size_t)1 ,_fp); + } + } + } + else // IMPLICIT VR + { + fwrite ( &lgr,(size_t)4 ,(size_t)1 ,_fp); + } +} + +/** + * \brief Writes in a file (according to the requested format) + * the value of a single gdcmHeaderEntry passed as argument. + * @param tag Pointer on the gdcmHeaderEntry to be written + * @param _fp Already open file pointer + * @param type type of the File to be written + */ +void gdcmDocument::WriteEntryValue(gdcmHeaderEntry *tag, FILE *_fp,FileType type) +{ + (void)type; + guint16 group = tag->GetGroup(); + std::string vr = tag->GetVR(); + guint32 lgr = tag->GetReadLength(); + + if (vr == "SQ") + // SeQuences have no value: + return; + if (group == 0xfffe) + // Delimiters have no associated value: + return; + + void *voidArea; + voidArea = tag->GetVoidArea(); + if (voidArea != NULL) + { // there is a 'non string' LUT, overlay, etc + fwrite ( voidArea,(size_t)lgr ,(size_t)1 ,_fp); // Elem value + return; + } + + if (vr == "US" || vr == "SS") + { + // some 'Short integer' fields may be mulivaluated + // each single value is separated from the next one by '\' + // we split the string and write each value as a short int + std::vector tokens; + tokens.erase(tokens.begin(),tokens.end()); // clean any previous value + Tokenize (tag->GetValue(), tokens, "\\"); + for (unsigned int i=0; i tokens; + tokens.erase(tokens.begin(),tokens.end()); // clean any previous value + Tokenize (tag->GetValue(), tokens, "\\"); + for (unsigned int i=0; iGetValue().c_str(), (size_t)lgr ,(size_t)1, _fp); // Elem value +} + +/** + * \brief Writes in a file (according to the requested format) + * a single gdcmHeaderEntry passed as argument. + * \sa WriteEntryValue, WriteEntryTagVRLength. + * @param tag Pointer on the gdcmHeaderEntry to be written + * @param _fp Already open file pointer + * @param type type of the File to be written + */ + +bool gdcmDocument::WriteEntry(gdcmHeaderEntry *tag, FILE *_fp,FileType type) +{ + guint32 length = tag->GetLength(); + + // The value of a tag MUST (see the DICOM norm) be an odd number of + // bytes. When this is not the case, pad with an additional byte: + if(length%2==1) + { + tag->SetValue(tag->GetValue()+"\0"); + tag->SetLength(tag->GetReadLength()+1); + } + + WriteEntryTagVRLength(tag, _fp, type); + WriteEntryValue(tag, _fp, type); + return true; +} + +/** + * \brief writes on disc according to the requested format + * (ACR-NEMA, ExplicitVR, ImplicitVR) the image + * using the Chained List + * \warning does NOT add the missing elements in the header : + * it's up to the user doing it ! + * (function CheckHeaderCoherence to be written) + * \warning DON'T try, right now, to write a DICOM image + * from an ACR Header (meta elements will be missing!) + * \sa WriteEntriesDeprecated (Special temporary method for Theralys) + * @param type type of the File to be written + * (ACR-NEMA, ExplicitVR, ImplicitVR) + * @param _fp already open file pointer + */ + +bool gdcmDocument::WriteEntries(FILE *_fp,FileType type) +{ + /// \todo (?) check write failures (after *each* fwrite) + + for (ListTag::iterator tag2=listEntries.begin(); + tag2 != listEntries.end(); + ++tag2) + { + if ( type == ACR ){ + if ((*tag2)->GetGroup() < 0x0008) + // Ignore pure DICOM V3 groups + continue; + if ((*tag2)->GetElement() %2) + // Ignore the "shadow" groups + continue; + if ((*tag2)->GetVR() == "SQ" ) // ignore Sequences + continue; + if ((*tag2)->GetSQDepthLevel() != 0) // Not only ignore the SQ element + continue; + } + if (! WriteEntry(*tag2,_fp,type) ) + return false; + } + return true; +} + +/** + * \brief writes on disc according to the requested format + * (ACR-NEMA, ExplicitVR, ImplicitVR) the image, + * using only the last synonym of each mutimap H Table post. + * \warning Uses the H Table, instead of the Chained List + * in order to be compliant with the old way to proceed + * (added elements taken in to account) + * Only THERALYS, during a transitory phase is supposed + * to use this method !!! + * \warning DON'T try, right now, to write a DICOM image + * from an ACR Header (meta elements will be missing!) + * \sa WriteEntries + * @param _fp already open file pointer + * @param type type of the File to be written + * (ACR-NEMA, ExplicitVR, ImplicitVR) + */ +void gdcmDocument::WriteEntriesDeprecated(FILE *_fp,FileType type) { + + // restent a tester les echecs en ecriture (apres chaque fwrite) + + for (TagHeaderEntryHT::iterator tag2=tagHT.begin(); + tag2 != tagHT.end(); + ++tag2){ + if ( type == ACR ){ + if ((*tag2->second).GetGroup() < 0x0008) continue; // ignore pure DICOM V3 groups + if ((*tag2->second).GetElement() %2) continue; // ignore shadow groups + if ((*tag2->second).GetVR() == "SQ" ) continue; // ignore Sequences + if ((*tag2->second).GetSQDepthLevel() != 0) continue; // Not only ignore the SQ element + } + if ( ! WriteEntry(tag2->second,_fp,type)) + break; + } +} + +/** + * \brief Swaps back the bytes of 4-byte long integer accordingly to + * processor order. + * @return The properly swaped 32 bits integer. + */ +guint32 gdcmDocument::SwapLong(guint32 a) { + switch (sw) { + case 0 : + break; + case 4321 : + a=( ((a<<24) & 0xff000000) | ((a<<8) & 0x00ff0000) | + ((a>>8) & 0x0000ff00) | ((a>>24) & 0x000000ff) ); + break; + + case 3412 : + a=( ((a<<16) & 0xffff0000) | ((a>>16) & 0x0000ffff) ); + break; + + case 2143 : + a=( ((a<<8) & 0xff00ff00) | ((a>>8) & 0x00ff00ff) ); + break; + default : + dbg.Error(" gdcmDocument::SwapLong : unset swap code"); + a=0; + } + return(a); +} + +/** + * \brief Unswaps back the bytes of 4-byte long integer accordingly to + * processor order. + * @return The properly unswaped 32 bits integer. + */ +guint32 gdcmDocument::UnswapLong(guint32 a) { + return (SwapLong(a)); +} + +/** + * \brief Swaps the bytes so they agree with the processor order + * @return The properly swaped 16 bits integer. + */ +guint16 gdcmDocument::SwapShort(guint16 a) { + if ( (sw==4321) || (sw==2143) ) + a =(((a<<8) & 0x0ff00) | ((a>>8)&0x00ff)); + return (a); +} + +/** + * \brief Unswaps the bytes so they agree with the processor order + * @return The properly unswaped 16 bits integer. + */ +guint16 gdcmDocument::UnswapShort(guint16 a) { + return (SwapShort(a)); +} + +//----------------------------------------------------------------------------- +// Private + +/** + * \brief Loads the element content if its length doesn't exceed + * the value specified with gdcmDocument::SetMaxSizeLoadEntry() + * @param Entry Header Entry (Dicom Element) to be dealt with + */ +void gdcmDocument::LoadHeaderEntry(gdcmHeaderEntry *Entry) { + size_t item_read; + guint16 group = Entry->GetGroup(); + std::string vr= Entry->GetVR(); + guint32 length = Entry->GetLength(); + + fseek(fp, (long)Entry->GetOffset(), SEEK_SET); + + // A SeQuence "contains" a set of Elements. + // (fffe e000) tells us an Element is beginning + // (fffe e00d) tells us an Element just ended + // (fffe e0dd) tells us the current SeQuence just ended + if( group == 0xfffe ) { + Entry->SetValue("gdcm::Skipped"); + return; + } + + // When the length is zero things are easy: + if ( length == 0 ) { + Entry->SetValue(""); + return; + } + + // The elements whose length is bigger than the specified upper bound + // are not loaded. Instead we leave a short notice of the offset of + // the element content and it's length. + if (length > MaxSizeLoadEntry) { + std::ostringstream s; + s << "gdcm::NotLoaded."; + s << " Address:" << (long)Entry->GetOffset(); + s << " Length:" << Entry->GetLength(); + s << " x(" << std::hex << Entry->GetLength() << ")"; + Entry->SetValue(s.str()); + return; + } + + // Any compacter code suggested (?) + if ( IsHeaderEntryAnInteger(Entry) ) { + guint32 NewInt; + std::ostringstream s; + int nbInt; + // When short integer(s) are expected, read and convert the following + // n *two characters properly i.e. as short integers as opposed to strings. + // Elements with Value Multiplicity > 1 + // contain a set of integers (not a single one) + if (vr == "US" || vr == "SS") { + nbInt = length / 2; + NewInt = ReadInt16(); + s << NewInt; + if (nbInt > 1){ + for (int i=1; i < nbInt; i++) { + s << '\\'; + NewInt = ReadInt16(); + s << NewInt; + } + } + } + // When integer(s) are expected, read and convert the following + // n * four characters properly i.e. as integers as opposed to strings. + // Elements with Value Multiplicity > 1 + // contain a set of integers (not a single one) + else if (vr == "UL" || vr == "SL") { + nbInt = length / 4; + NewInt = ReadInt32(); + s << NewInt; + if (nbInt > 1) { + for (int i=1; i < nbInt; i++) { + s << '\\'; + NewInt = ReadInt32(); + s << NewInt; + } + } + } +#ifdef GDCM_NO_ANSI_STRING_STREAM + s << std::ends; // to avoid oddities on Solaris +#endif //GDCM_NO_ANSI_STRING_STREAM + + Entry->SetValue(s.str()); + return; + } + + // We need an additional byte for storing \0 that is not on disk + std::string NewValue(length,0); + item_read = fread(&(NewValue[0]), (size_t)length, (size_t)1, fp); + if ( item_read != 1 ) { + dbg.Verbose(1, "gdcmDocument::LoadElementValue","unread element value"); + Entry->SetValue("gdcm::UnRead"); + return; + } + + if( (vr == "UI") ) // Because of correspondance with the VR dic + Entry->SetValue(NewValue.c_str()); + else + Entry->SetValue(NewValue); +} + +/** + * \brief add a new Dicom Element pointer to + * the H Table and at the end of the chained List + * \warning push_bash in listEntries ONLY during ParseHeader + * \todo something to allow further Elements addition, + * (at their right place in the chained list) + * when position to be taken care of + * @param newHeaderEntry + */ +void gdcmDocument::AddHeaderEntry(gdcmHeaderEntry *newHeaderEntry) { + tagHT.insert( PairHT( newHeaderEntry->GetKey(),newHeaderEntry) ); + listEntries.push_back(newHeaderEntry); + wasUpdated = 1; +} + +/** + * \brief Find the value Length of the passed Header Entry + * @param Entry Header Entry whose length of the value shall be loaded. + */ + void gdcmDocument::FindHeaderEntryLength (gdcmHeaderEntry *Entry) { + guint16 element = Entry->GetElement(); + //guint16 group = Entry->GetGroup(); //FIXME + std::string vr = Entry->GetVR(); + guint16 length16; + + + if ( (filetype == ExplicitVR) && (! Entry->IsImplicitVR()) ) + { + if ( (vr=="OB") || (vr=="OW") || (vr=="SQ") || (vr=="UN") ) + { + // The following reserved two bytes (see PS 3.5-2001, section + // 7.1.2 Data element structure with explicit vr p27) must be + // skipped before proceeding on reading the length on 4 bytes. + fseek(fp, 2L, SEEK_CUR); + guint32 length32 = ReadInt32(); + + if ( (vr == "OB") && (length32 == 0xffffffff) ) + { + Entry->SetLength(FindHeaderEntryLengthOB()); + return; + } + FixHeaderEntryFoundLength(Entry, length32); + return; + } + + // Length is encoded on 2 bytes. + length16 = ReadInt16(); + + // We can tell the current file is encoded in big endian (like + // Data/US-RGB-8-epicard) when we find the "Transfer Syntax" tag + // and it's value is the one of the encoding of a big endian file. + // In order to deal with such big endian encoded files, we have + // (at least) two strategies: + // * when we load the "Transfer Syntax" tag with value of big endian + // encoding, we raise the proper flags. Then we wait for the end + // of the META group (0x0002) among which is "Transfer Syntax", + // before switching the swap code to big endian. We have to postpone + // the switching of the swap code since the META group is fully encoded + // in little endian, and big endian coding only starts at the next + // group. The corresponding code can be hard to analyse and adds + // many additional unnecessary tests for regular tags. + // * the second strategy consists in waiting for trouble, that shall + // appear when we find the first group with big endian encoding. This + // is easy to detect since the length of a "Group Length" tag (the + // ones with zero as element number) has to be of 4 (0x0004). When we + // encounter 1024 (0x0400) chances are the encoding changed and we + // found a group with big endian encoding. + // We shall use this second strategy. In order to make sure that we + // can interpret the presence of an apparently big endian encoded + // length of a "Group Length" without committing a big mistake, we + // add an additional check: we look in the already parsed elements + // for the presence of a "Transfer Syntax" whose value has to be "big + // endian encoding". When this is the case, chances are we have got our + // hands on a big endian encoded file: we switch the swap code to + // big endian and proceed... + if ( (element == 0x0000) && (length16 == 0x0400) ) + { + if ( ! IsExplicitVRBigEndianTransferSyntax() ) + { + dbg.Verbose(0, "gdcmDocument::FindLength", "not explicit VR"); + errno = 1; + return; + } + length16 = 4; + SwitchSwapToBigEndian(); + // Restore the unproperly loaded values i.e. the group, the element + // and the dictionary entry depending on them. + guint16 CorrectGroup = SwapShort(Entry->GetGroup()); + guint16 CorrectElem = SwapShort(Entry->GetElement()); + gdcmDictEntry * NewTag = GetDictEntryByNumber(CorrectGroup, + CorrectElem); + if (!NewTag) + { + // This correct tag is not in the dictionary. Create a new one. + NewTag = NewVirtualDictEntry(CorrectGroup, CorrectElem); + } + // FIXME this can create a memory leaks on the old entry that be + // left unreferenced. + Entry->SetDictEntry(NewTag); + } + + // Heuristic: well some files are really ill-formed. + if ( length16 == 0xffff) + { + length16 = 0; + //dbg.Verbose(0, "gdcmDocument::FindLength", + // "Erroneous element length fixed."); + // Actually, length= 0xffff means that we deal with + // Unknown Sequence Length + } + FixHeaderEntryFoundLength(Entry, (guint32)length16); + return; + } + else + { + // Either implicit VR or a non DICOM conformal (see note below) explicit + // VR that ommited the VR of (at least) this element. Farts happen. + // [Note: according to the part 5, PS 3.5-2001, section 7.1 p25 + // on Data elements "Implicit and Explicit VR Data Elements shall + // not coexist in a Data Set and Data Sets nested within it".] + // Length is on 4 bytes. + + FixHeaderEntryFoundLength(Entry, ReadInt32()); + return; + } +} + +/** + * \brief Find the Value Representation of the current Dicom Element. + * @param Entry + */ +void gdcmDocument::FindHeaderEntryVR( gdcmHeaderEntry *Entry) +{ + if (filetype != ExplicitVR) + return; + + char VR[3]; + + long PositionOnEntry = ftell(fp); + // Warning: we believe this is explicit VR (Value Representation) because + // we used a heuristic that found "UL" in the first tag. Alas this + // doesn't guarantee that all the tags will be in explicit VR. In some + // cases (see e-film filtered files) one finds implicit VR tags mixed + // within an explicit VR file. Hence we make sure the present tag + // is in explicit VR and try to fix things if it happens not to be + // the case. + + (void)fread (&VR, (size_t)2,(size_t)1, fp); + VR[2]=0; + if(!CheckHeaderEntryVR(Entry,VR)) + { + fseek(fp, PositionOnEntry, SEEK_SET); + // When this element is known in the dictionary we shall use, e.g. for + // the semantics (see the usage of IsAnInteger), the VR proposed by the + // dictionary entry. Still we have to flag the element as implicit since + // we know now our assumption on expliciteness is not furfilled. + // avoid . + if ( Entry->IsVRUnknown() ) + Entry->SetVR("Implicit"); + Entry->SetImplicitVR(); + } +} + +/** + * \brief Check the correspondance between the VR of the header entry + * and the taken VR. If they are different, the header entry is + * updated with the new VR. + * @param Entry Header Entry to check + * @param vr Dicom Value Representation + * @return false if the VR is incorrect of if the VR isn't referenced + * otherwise, it returns true +*/ +bool gdcmDocument::CheckHeaderEntryVR(gdcmHeaderEntry *Entry, VRKey vr) +{ + char msg[100]; // for sprintf + bool RealExplicit = true; + + // Assume we are reading a falsely explicit VR file i.e. we reached + // a tag where we expect reading a VR but are in fact we read the + // first to bytes of the length. Then we will interogate (through find) + // the dicom_vr dictionary with oddities like "\004\0" which crashes + // both GCC and VC++ implementations of the STL map. Hence when the + // expected VR read happens to be non-ascii characters we consider + // we hit falsely explicit VR tag. + + if ( (!isalpha(vr[0])) && (!isalpha(vr[1])) ) + RealExplicit = false; + + // CLEANME searching the dicom_vr at each occurence is expensive. + // PostPone this test in an optional integrity check at the end + // of parsing or only in debug mode. + if ( RealExplicit && !gdcmGlobal::GetVR()->Count(vr) ) + RealExplicit= false; + + if ( !RealExplicit ) + { + // We thought this was explicit VR, but we end up with an + // implicit VR tag. Let's backtrack. + sprintf(msg,"Falsely explicit vr file (%04x,%04x)\n", + Entry->GetGroup(),Entry->GetElement()); + dbg.Verbose(1, "gdcmDocument::FindVR: ",msg); + if (Entry->GetGroup()%2 && Entry->GetElement() == 0x0000) { // Group length is UL ! + gdcmDictEntry* NewEntry = NewVirtualDictEntry( + Entry->GetGroup(),Entry->GetElement(), + "UL","FIXME","Group Length"); + Entry->SetDictEntry(NewEntry); + } + return(false); + } + + if ( Entry->IsVRUnknown() ) + { + // When not a dictionary entry, we can safely overwrite the VR. + if (Entry->GetElement() == 0x0000) { // Group length is UL ! + Entry->SetVR("UL"); + } else { + Entry->SetVR(vr); + } + } + else if ( Entry->GetVR() != vr ) + { + // The VR present in the file and the dictionary disagree. We assume + // the file writer knew best and use the VR of the file. Since it would + // be unwise to overwrite the VR of a dictionary (since it would + // compromise it's next user), we need to clone the actual DictEntry + // and change the VR for the read one. + gdcmDictEntry* NewEntry = NewVirtualDictEntry( + Entry->GetGroup(),Entry->GetElement(), + vr,"FIXME",Entry->GetName()); + Entry->SetDictEntry(NewEntry); + } + return(true); +} + +/** + * \brief Get the transformed value of the header entry. The VR value + * is used to define the transformation to operate on the value + * \warning NOT end user intended method ! + * @param Entry + * @return Transformed entry value + */ +std::string gdcmDocument::GetHeaderEntryValue(gdcmHeaderEntry *Entry) +{ + if ( (IsHeaderEntryAnInteger(Entry)) && (Entry->IsImplicitVR()) ) + { + std::string val=Entry->GetValue(); + std::string vr=Entry->GetVR(); + guint32 length = Entry->GetLength(); + std::ostringstream s; + int nbInt; + + // When short integer(s) are expected, read and convert the following + // n * 2 bytes properly i.e. as a multivaluated strings + // (each single value is separated fromthe next one by '\' + // as usual for standard multivaluated filels + // Elements with Value Multiplicity > 1 + // contain a set of short integers (not a single one) + + if (vr == "US" || vr == "SS") + { + guint16 NewInt16; + + nbInt = length / 2; + for (int i=0; i < nbInt; i++) + { + if(i!=0) + s << '\\'; + NewInt16 = (val[2*i+0]&0xFF)+((val[2*i+1]&0xFF)<<8); + NewInt16 = SwapShort(NewInt16); + s << NewInt16; + } + } + + // When integer(s) are expected, read and convert the following + // n * 4 bytes properly i.e. as a multivaluated strings + // (each single value is separated fromthe next one by '\' + // as usual for standard multivaluated filels + // Elements with Value Multiplicity > 1 + // contain a set of integers (not a single one) + else if (vr == "UL" || vr == "SL") + { + guint32 NewInt32; + + nbInt = length / 4; + for (int i=0; i < nbInt; i++) + { + if(i!=0) + s << '\\'; + NewInt32= (val[4*i+0]&0xFF)+((val[4*i+1]&0xFF)<<8)+ + ((val[4*i+2]&0xFF)<<16)+((val[4*i+3]&0xFF)<<24); + NewInt32=SwapLong(NewInt32); + s << NewInt32; + } + } +#ifdef GDCM_NO_ANSI_STRING_STREAM + s << std::ends; // to avoid oddities on Solaris +#endif //GDCM_NO_ANSI_STRING_STREAM + return(s.str()); + } + + return(Entry->GetValue()); +} + +/** + * \brief Get the reverse transformed value of the header entry. The VR + * value is used to define the reverse transformation to operate on + * the value + * \warning NOT end user intended method ! + * @param Entry + * @return Reverse transformed entry value + */ +std::string gdcmDocument::GetHeaderEntryUnvalue(gdcmHeaderEntry *Entry) +{ + if ( (IsHeaderEntryAnInteger(Entry)) && (Entry->IsImplicitVR()) ) + { + std::string vr=Entry->GetVR(); + std::ostringstream s; + std::vector tokens; + + if (vr == "US" || vr == "SS") + { + guint16 NewInt16; + + tokens.erase(tokens.begin(),tokens.end()); // clean any previous value + Tokenize (Entry->GetValue(), tokens, "\\"); + for (unsigned int i=0; i>8)&0xFF); + } + tokens.clear(); + } + if (vr == "UL" || vr == "SL") + { + guint32 NewInt32; + + tokens.erase(tokens.begin(),tokens.end()); // clean any previous value + Tokenize (Entry->GetValue(), tokens, "\\"); + for (unsigned int i=0; i>8)&0xFF) + <<(char)((NewInt32>>16)&0xFF)<<(char)((NewInt32>>24)&0xFF); + } + tokens.clear(); + } + +#ifdef GDCM_NO_ANSI_STRING_STREAM + s << std::ends; // to avoid oddities on Solaris +#endif //GDCM_NO_ANSI_STRING_STREAM + return(s.str()); + } + + return(Entry->GetValue()); +} + +/** + * \brief Skip a given Header Entry + * \warning NOT end user intended method ! + * @param entry + */ +void gdcmDocument::SkipHeaderEntry(gdcmHeaderEntry *entry) +{ + SkipBytes(entry->GetLength()); +} + +/** + * \brief When the length of an element value is obviously wrong (because + * the parser went Jabberwocky) one can hope improving things by + * applying this heuristic. + */ +void gdcmDocument::FixHeaderEntryFoundLength(gdcmHeaderEntry *Entry, guint32 FoundLength) +{ + Entry->SetReadLength(FoundLength); // will be updated only if a bug is found + if ( FoundLength == 0xffffffff) { + FoundLength = 0; + } + + guint16 gr =Entry->GetGroup(); + guint16 el =Entry->GetElement(); + + if (FoundLength%2) { + std::ostringstream s; + s << "Warning : Tag with uneven length " << FoundLength + << " in x(" << std::hex << gr << "," << el <<")" << std::dec; + dbg.Verbose(0,s.str().c_str()); + } + + // Sorry for the patch! + // XMedCom did the trick to read some naughty GE images ... + if (FoundLength == 13) { + // The following 'if' will be removed when there is no more + // images on Creatis HDs with a 13 length for Manufacturer... + if ( (Entry->GetGroup() != 0x0008) || + ( (Entry->GetElement() != 0x0070) && (Entry->GetElement() != 0x0080) ) ){ + // end of remove area + FoundLength =10; + Entry->SetReadLength(10); // a bug is to be fixed + } + } + + // to fix some garbage 'Leonardo' Siemens images + // May be commented out to avoid overhead + else if ( (Entry->GetGroup() == 0x0009) && + ( (Entry->GetElement() == 0x1113) || (Entry->GetElement() == 0x1114) ) ){ + FoundLength =4; + Entry->SetReadLength(4); // a bug is to be fixed + } + // end of fix + + // to try to 'go inside' SeQuences (with length), and not to skip them + else if ( Entry->GetVR() == "SQ") + { + if (enableSequences) // only if the user does want to ! + FoundLength =0; // ReadLength is unchanged + } + + // we found a 'delimiter' element + // fffe|xxxx is just a marker, we don't take its length into account + else if(Entry->GetGroup() == 0xfffe) + { + // *normally, fffe|0000 doesn't exist ! + if( Entry->GetElement() != 0x0000 ) // gdcm-MR-PHILIPS-16-Multi-Seq.dcm + // causes extra troubles :-( + FoundLength =0; + } + + Entry->SetUsableLength(FoundLength); +} + +/** + * \brief Apply some heuristics to predict whether the considered + * element value contains/represents an integer or not. + * @param Entry The element value on which to apply the predicate. + * @return The result of the heuristical predicate. + */ +bool gdcmDocument::IsHeaderEntryAnInteger(gdcmHeaderEntry *Entry) { + guint16 element = Entry->GetElement(); + guint16 group = Entry->GetGroup(); + std::string vr = Entry->GetVR(); + guint32 length = Entry->GetLength(); + // When we have some semantics on the element we just read, and if we + // a priori know we are dealing with an integer, then we shall be + // able to swap it's element value properly. + if ( element == 0 ) // This is the group length of the group + { + if (length == 4) + return true; + else + { + std::ostringstream s; + int filePosition = ftell(fp); + s << "Erroneous Group Length element length on : (" \ + << std::hex << group << " , " << element + << ") -before- position x(" << filePosition << ")" + << "lgt : " << length; + // These 2 lines commented out : a *very dirty* patch + // to go on PrintHeader'ing gdcm-MR-PHILIPS-16-Multi-Seq.dcm. + // have a glance at offset x(8336) ... + // For *regular* headers, the test is useless.. + // lets's print a warning message and go on, + // instead of giving up with an error message + + //std::cout << s.str().c_str() << std::endl; + // dbg.Error("gdcmDocument::IsHeaderEntryAnInteger", + // s.str().c_str()); + } + } + if ( (vr == "UL") || (vr == "US") || (vr == "SL") || (vr == "SS") ) + return true; + + return false; +} +/** + * \brief Find the Length till the next sequence delimiter + * \warning NOT end user intended method ! + * @return + */ + + guint32 gdcmDocument::FindHeaderEntryLengthOB(void) { + // See PS 3.5-2001, section A.4 p. 49 on encapsulation of encoded pixel data. + guint16 g; + guint16 n; + long PositionOnEntry = ftell(fp); + bool FoundSequenceDelimiter = false; + guint32 TotalLength = 0; + guint32 ItemLength; + + while ( ! FoundSequenceDelimiter) + { + g = ReadInt16(); + n = ReadInt16(); + if (errno == 1) + return 0; + TotalLength += 4; // We even have to decount the group and element + + if ( g != 0xfffe && g!=0xb00c ) //for bogus header + { + char msg[100]; // for sprintf. Sorry + sprintf(msg,"wrong group (%04x) for an item sequence (%04x,%04x)\n",g, g,n); + dbg.Verbose(1, "gdcmDocument::FindLengthOB: ",msg); + errno = 1; + return 0; + } + if ( n == 0xe0dd || ( g==0xb00c && n==0x0eb6 ) ) // for bogus header + FoundSequenceDelimiter = true; + else if ( n != 0xe000 ) + { + char msg[100]; // for sprintf. Sorry + sprintf(msg,"wrong element (%04x) for an item sequence (%04x,%04x)\n", + n, g,n); + dbg.Verbose(1, "gdcmDocument::FindLengthOB: ",msg); + errno = 1; + return 0; + } + ItemLength = ReadInt32(); + TotalLength += ItemLength + 4; // We add 4 bytes since we just read + // the ItemLength with ReadInt32 + SkipBytes(ItemLength); + } + fseek(fp, PositionOnEntry, SEEK_SET); + return TotalLength; +} + +/** + * \brief Reads a supposed to be 16 Bits integer + * (swaps it depending on processor endianity) + * @return read value + */ +guint16 gdcmDocument::ReadInt16(void) { + guint16 g; + size_t item_read; + item_read = fread (&g, (size_t)2,(size_t)1, fp); + if ( item_read != 1 ) { + if(ferror(fp)) + dbg.Verbose(0, "gdcmDocument::ReadInt16", " File Error"); + errno = 1; + return 0; + } + errno = 0; + g = SwapShort(g); + return g; +} + +/** + * \brief Reads a supposed to be 32 Bits integer + * (swaps it depending on processor endianity) + * @return read value + */ +guint32 gdcmDocument::ReadInt32(void) { + guint32 g; + size_t item_read; + item_read = fread (&g, (size_t)4,(size_t)1, fp); + if ( item_read != 1 ) { + if(ferror(fp)) + dbg.Verbose(0, "gdcmDocument::ReadInt32", " File Error"); + errno = 1; + return 0; + } + errno = 0; + g = SwapLong(g); + return g; +} + +/** + * \brief skips bytes inside the source file + * \warning NOT end user intended method ! + * @return + */ +void gdcmDocument::SkipBytes(guint32 NBytes) { + //FIXME don't dump the returned value + (void)fseek(fp, (long)NBytes, SEEK_CUR); +} + +/** + * \brief Loads all the needed Dictionaries + * \warning NOT end user intended method ! + */ +void gdcmDocument::Initialise(void) +{ + RefPubDict = gdcmGlobal::GetDicts()->GetDefaultPubDict(); + RefShaDict = (gdcmDict*)0; +} + +/** + * \brief Discover what the swap code is (among little endian, big endian, + * bad little endian, bad big endian). + * sw is set + * @return false when we are absolutely sure + * it's neither ACR-NEMA nor DICOM + * true when we hope ours assuptions are OK + */ +bool gdcmDocument::CheckSwap() { + + // The only guaranted way of finding the swap code is to find a + // group tag since we know it's length has to be of four bytes i.e. + // 0x00000004. Finding the swap code in then straigthforward. Trouble + // occurs when we can't find such group... + + guint32 x=4; // x : for ntohs + bool net2host; // true when HostByteOrder is the same as NetworkByteOrder + guint32 s32; + guint16 s16; + + int lgrLue; + char *entCur; + char deb[HEADER_LENGTH_TO_READ]; + + // First, compare HostByteOrder and NetworkByteOrder in order to + // determine if we shall need to swap bytes (i.e. the Endian type). + if (x==ntohs(x)) + net2host = true; + else + net2host = false; + + // The easiest case is the one of a DICOM header, since it possesses a + // file preamble where it suffice to look for the string "DICM". + lgrLue = fread(deb, 1, HEADER_LENGTH_TO_READ, fp); + + entCur = deb + 128; + if(memcmp(entCur, "DICM", (size_t)4) == 0) { + dbg.Verbose(1, "gdcmDocument::CheckSwap:", "looks like DICOM Version3"); + + // Next, determine the value representation (VR). Let's skip to the + // first element (0002, 0000) and check there if we find "UL" + // - or "OB" if the 1st one is (0002,0001) -, + // in which case we (almost) know it is explicit VR. + // WARNING: if it happens to be implicit VR then what we will read + // is the length of the group. If this ascii representation of this + // length happens to be "UL" then we shall believe it is explicit VR. + // FIXME: in order to fix the above warning, we could read the next + // element value (or a couple of elements values) in order to make + // sure we are not commiting a big mistake. + // We need to skip : + // * the 128 bytes of File Preamble (often padded with zeroes), + // * the 4 bytes of "DICM" string, + // * the 4 bytes of the first tag (0002, 0000),or (0002, 0001) + // i.e. a total of 136 bytes. + entCur = deb + 136; + + // FIXME : FIXME: + // Sometimes (see : gdcmData/icone.dcm) group 0x0002 *is* Explicit VR, + // but elem 0002,0010 (Transfert Syntax) tells us the file is + // *Implicit* VR. -and it is !- + + if( (memcmp(entCur, "UL", (size_t)2) == 0) || + (memcmp(entCur, "OB", (size_t)2) == 0) || + (memcmp(entCur, "UI", (size_t)2) == 0) || + (memcmp(entCur, "CS", (size_t)2) == 0) ) // CS, to remove later + // when Write DCM *adds* + // FIXME + // Use gdcmDocument::dicom_vr to test all the possibilities + // instead of just checking for UL, OB and UI !? group 0000 + { + filetype = ExplicitVR; + dbg.Verbose(1, "gdcmDocument::CheckSwap:", + "explicit Value Representation"); + } + else + { + filetype = ImplicitVR; + dbg.Verbose(1, "gdcmDocument::CheckSwap:", + "not an explicit Value Representation"); + } + + if (net2host) + { + sw = 4321; + dbg.Verbose(1, "gdcmDocument::CheckSwap:", + "HostByteOrder != NetworkByteOrder"); + } + else + { + sw = 0; + dbg.Verbose(1, "gdcmDocument::CheckSwap:", + "HostByteOrder = NetworkByteOrder"); + } + + // Position the file position indicator at first tag (i.e. + // after the file preamble and the "DICM" string). + rewind(fp); + fseek (fp, 132L, SEEK_SET); + return true; + } // End of DicomV3 + + // Alas, this is not a DicomV3 file and whatever happens there is no file + // preamble. We can reset the file position indicator to where the data + // is (i.e. the beginning of the file). + dbg.Verbose(1, "gdcmDocument::CheckSwap:", "not a DICOM Version3 file"); + rewind(fp); + + // Our next best chance would be to be considering a 'clean' ACR/NEMA file. + // By clean we mean that the length of the first tag is written down. + // If this is the case and since the length of the first group HAS to be + // four (bytes), then determining the proper swap code is straightforward. + + entCur = deb + 4; + // We assume the array of char we are considering contains the binary + // representation of a 32 bits integer. Hence the following dirty + // trick : + s32 = *((guint32 *)(entCur)); + + switch (s32) { + case 0x00040000 : + sw = 3412; + filetype = ACR; + return true; + case 0x04000000 : + sw = 4321; + filetype = ACR; + return true; + case 0x00000400 : + sw = 2143; + filetype = ACR; + return true; + case 0x00000004 : + sw = 0; + filetype = ACR; + return true; + default : + + // We are out of luck. It is not a DicomV3 nor a 'clean' ACR/NEMA file. + // It is time for despaired wild guesses. + // So, let's check if this file wouldn't happen to be 'dirty' ACR/NEMA, + // i.e. the 'group length' element is not present : + + // check the supposed to be 'group number' + // 0x0002 or 0x0004 or 0x0008 + // to determine ' sw' value . + // Only 0 or 4321 will be possible + // (no oportunity to check for the formerly well known + // ACR-NEMA 'Bad Big Endian' or 'Bad Little Endian' + // if unsuccessfull (i.e. neither 0x0002 nor 0x0200 etc -4, 8-) + // the file IS NOT ACR-NEMA nor DICOM V3 + // Find a trick to tell it the caller... + + s16 = *((guint16 *)(deb)); + + switch (s16) { + case 0x0002 : + case 0x0004 : + case 0x0008 : + sw = 0; + filetype = ACR; + return true; + case 0x0200 : + case 0x0400 : + case 0x0800 : + sw = 4321; + filetype = ACR; + return true; + default : + dbg.Verbose(0, "gdcmDocument::CheckSwap:", + "ACR/NEMA unfound swap info (Really hopeless !)"); + filetype = Unknown; + return false; + } + + // Then the only info we have is the net2host one. + //if (! net2host ) + // sw = 0; + //else + // sw = 4321; + //return; + } +} + +/** + * \brief Restore the unproperly loaded values i.e. the group, the element + * and the dictionary entry depending on them. + */ +void gdcmDocument::SwitchSwapToBigEndian(void) +{ + dbg.Verbose(1, "gdcmDocument::SwitchSwapToBigEndian", + "Switching to BigEndian mode."); + if ( sw == 0 ) + { + sw = 4321; + return; + } + if ( sw == 4321 ) + { + sw = 0; + return; + } + if ( sw == 3412 ) + { + sw = 2143; + return; + } + if ( sw == 2143 ) + sw = 3412; +} + +/** + * \brief during parsing, Header Elements too long are not loaded in memory + * @param NewSize + */ +void gdcmDocument::SetMaxSizeLoadEntry(long NewSize) +{ + if (NewSize < 0) + return; + if ((guint32)NewSize >= (guint32)0xffffffff) + { + MaxSizeLoadEntry = 0xffffffff; + return; + } + MaxSizeLoadEntry = NewSize; +} + + +/** + * \brief Header Elements too long will not be printed + * \todo See comments of \ref gdcmDocument::MAX_SIZE_PRINT_ELEMENT_VALUE + * @param NewSize + */ +void gdcmDocument::SetMaxSizePrintEntry(long NewSize) +{ + if (NewSize < 0) + return; + if ((guint32)NewSize >= (guint32)0xffffffff) + { + MaxSizePrintEntry = 0xffffffff; + return; + } + MaxSizePrintEntry = NewSize; +} + +/** + * \brief Searches both the public and the shadow dictionary (when they + * exist) for the presence of the DictEntry with given name. + * The public dictionary has precedence on the shadow one. + * @param Name name of the searched DictEntry + * @return Corresponding DictEntry when it exists, NULL otherwise. + */ +gdcmDictEntry *gdcmDocument::GetDictEntryByName(std::string Name) +{ + gdcmDictEntry *found = (gdcmDictEntry *)0; + if (!RefPubDict && !RefShaDict) + { + dbg.Verbose(0, "gdcmDocument::GetDictEntry", + "we SHOULD have a default dictionary"); + } + if (RefPubDict) + { + found = RefPubDict->GetDictEntryByName(Name); + if (found) + return found; + } + if (RefShaDict) + { + found = RefShaDict->GetDictEntryByName(Name); + if (found) + return found; + } + return found; +} + +/** + * \brief Searches both the public and the shadow dictionary (when they + * exist) for the presence of the DictEntry with given + * group and element. The public dictionary has precedence on the + * shadow one. + * @param group group of the searched DictEntry + * @param element element of the searched DictEntry + * @return Corresponding DictEntry when it exists, NULL otherwise. + */ +gdcmDictEntry *gdcmDocument::GetDictEntryByNumber(guint16 group,guint16 element) +{ + gdcmDictEntry *found = (gdcmDictEntry *)0; + if (!RefPubDict && !RefShaDict) + { + dbg.Verbose(0, "gdcmDocument::GetDictEntry", + "we SHOULD have a default dictionary"); + } + if (RefPubDict) + { + found = RefPubDict->GetDictEntryByNumber(group, element); + if (found) + return found; + } + if (RefShaDict) + { + found = RefShaDict->GetDictEntryByNumber(group, element); + if (found) + return found; + } + return found; +} + +/** + * \brief Read the next tag but WITHOUT loading it's value + * @return On succes the newly created HeaderEntry, NULL on failure. + */ +gdcmHeaderEntry *gdcmDocument::ReadNextHeaderEntry(void) { + guint16 g,n; + gdcmHeaderEntry *NewEntry; + g = ReadInt16(); + n = ReadInt16(); + + if (errno == 1) + // We reached the EOF (or an error occured) therefore + // header parsing has to be considered as finished. + return (gdcmHeaderEntry *)0; + +// Pb : how to propagate the element length (used in SkipHeaderEntry) +// direct call to SkipBytes ? + +// if (ignoreShadow == 1 && g%2 ==1) + // if user wants to skip shadow groups + // and current element *is* a shadow element + // we don't create anything +// return (gdcmHeaderEntry *)1; // to tell caller it's NOT finished + + NewEntry = NewHeaderEntryByNumber(g, n); + FindHeaderEntryVR(NewEntry); + FindHeaderEntryLength(NewEntry); + + if (errno == 1) { + // Call it quits + return NULL; + } + NewEntry->SetOffset(ftell(fp)); + return NewEntry; +} + +/** + * \brief Build a new Element Value from all the low level arguments. + * Check for existence of dictionary entry, and build + * a default one when absent. + * @param Name Name of the underlying DictEntry + */ +gdcmHeaderEntry *gdcmDocument::NewHeaderEntryByName(std::string Name) +{ + gdcmDictEntry *NewTag = GetDictEntryByName(Name); + if (!NewTag) + NewTag = NewVirtualDictEntry(0xffff, 0xffff, "LO", "unkn", Name); + + gdcmHeaderEntry* NewEntry = new gdcmHeaderEntry(NewTag); + if (!NewEntry) + { + dbg.Verbose(1, "gdcmDocument::ObtainHeaderEntryByName", + "failed to allocate gdcmHeaderEntry"); + return (gdcmHeaderEntry *)0; + } + return NewEntry; +} + +/** + * \brief Request a new virtual dict entry to the dict set + * @param group group of the underlying DictEntry + * @param element element of the underlying DictEntry + * @param vr VR of the underlying DictEntry + * @param fourth owner group + * @param name english name + */ +gdcmDictEntry *gdcmDocument::NewVirtualDictEntry(guint16 group, guint16 element, + std::string vr, + std::string fourth, + std::string name) +{ + return gdcmGlobal::GetDicts()->NewVirtualDictEntry(group,element,vr,fourth,name); +} + +/** + * \brief Build a new Element Value from all the low level arguments. + * Check for existence of dictionary entry, and build + * a default one when absent. + * @param Group group of the underlying DictEntry + * @param Elem element of the underlying DictEntry + */ +gdcmHeaderEntry *gdcmDocument::NewHeaderEntryByNumber(guint16 Group, guint16 Elem) +{ + // Find out if the tag we encountered is in the dictionaries: + gdcmDictEntry *DictEntry = GetDictEntryByNumber(Group, Elem); + if (!DictEntry) + DictEntry = NewVirtualDictEntry(Group, Elem); + + gdcmHeaderEntry *NewEntry = new gdcmHeaderEntry(DictEntry); + if (!NewEntry) + { + dbg.Verbose(1, "gdcmDocument::NewHeaderEntryByNumber", + "failed to allocate gdcmHeaderEntry"); + return NULL; + } + return NewEntry; +} + +/// \todo Never used; commented out, waiting for removal. +/** + * \brief Small utility function that creates a new manually crafted + * (as opposed as read from the file) gdcmHeaderEntry with user + * specified name and adds it to the public tag hash table. + * \note A fake TagKey is generated so the PubDict can keep it's coherence. + * @param NewTagName The name to be given to this new tag. + * @param VR The Value Representation to be given to this new tag. + * @return The newly hand crafted Element Value. + */ +//gdcmHeaderEntry *gdcmDocument::NewManualHeaderEntryToPubDict(std::string NewTagName, +// std::string VR) +//{ +// gdcmHeaderEntry *NewEntry = NULL; +// guint32 StuffGroup = 0xffff; // Group to be stuffed with additional info +// guint32 FreeElem = 0; +// gdcmDictEntry *DictEntry = NULL; +// +// FreeElem = GenerateFreeTagKeyInGroup(StuffGroup); +// if (FreeElem == UINT32_MAX) +// { +// dbg.Verbose(1, "gdcmHeader::NewManualHeaderEntryToPubDict", +// "Group 0xffff in Public Dict is full"); +// return NULL; +// } +// +// DictEntry = NewVirtualDictEntry(StuffGroup, FreeElem, +// VR, "GDCM", NewTagName); +// NewEntry = new gdcmHeaderEntry(DictEntry); +// AddHeaderEntry(NewEntry); +// return NewEntry; +//} + +/** + * \brief Generate a free TagKey i.e. a TagKey that is not present + * in the TagHt dictionary. + * @param group The generated tag must belong to this group. + * @return The element of tag with given group which is fee. + */ +guint32 gdcmDocument::GenerateFreeTagKeyInGroup(guint16 group) +{ + for (guint32 elem = 0; elem < UINT32_MAX; elem++) + { + TagKey key = gdcmDictEntry::TranslateToKey(group, elem); + if (tagHT.count(key) == 0) + return elem; + } + return UINT32_MAX; +} + +//----------------------------------------------------------------------------- diff --git a/src/gdcmDocument.h b/src/gdcmDocument.h new file mode 100644 index 00000000..7fc5c143 --- /dev/null +++ b/src/gdcmDocument.h @@ -0,0 +1,272 @@ +// gdcmDocument.h +//----------------------------------------------------------------------------- +#ifndef GDCMDOCUMENT_H +#define GDCMDOCUMENT_H + +#include "gdcmCommon.h" + +#include "gdcmVR.h" +#include "gdcmTS.h" +#include "gdcmException.h" +#include "gdcmDictSet.h" +#include "gdcmDocEntry.h" + +#include +#include + + + +//----------------------------------------------------------------------------- +typedef std::string VRKey; +typedef std::string VRAtr; +typedef std::map VRHT; // Value Representation Hash Table + +typedef std::multimap TagDocEntryHT; +typedef std::pair PairHT; +typedef std::pair IterHT; +/// for linking together the Elements +typedef std::list ListTag; + +typedef std::string GroupKey; +typedef std::map GroupHT; + +//----------------------------------------------------------------------------- +/** + * \brief used by both gdcmHeader and gdcmDicomDir + */ +class GDCM_EXPORT gdcmDocument : public gdcmElementSet +{ +private: + /// Public dictionary used to parse this header + gdcmDict *RefPubDict; + + /// \brief Optional "shadow dictionary" (private elements) used to parse + /// this header + gdcmDict *RefShaDict; + + /// Equals 1 if a gdcmDocEntry was added post parsing + int wasUpdated; + + /// \brief Equals =1 if user wants to skip shadow groups while parsing + /// (to save space) + int ignoreShadow; + + /// \brief Size threshold above which an element value will NOT be loaded + /// in memory (to avoid loading the image/volume itself). By default, + /// this upper bound is fixed to 1024 bytes (which might look reasonable + /// when one considers the definition of the various VR contents). + guint32 MaxSizeLoadEntry; + + /// \brief Size threshold above which an element value will NOT be *printed* + /// in order no to polute the screen output. By default, this upper bound + /// is fixed to 64 bytes. + guint32 MaxSizePrintEntry; + +protected: + /// Refering underlying filename. + std::string filename; + + /// \brief SWap code (e.g. Big Endian, Little Endian, Bad Big Endian, + /// Bad Little Endian) according to the processor Endianity and + /// what is written on disc. + int sw; + + /// File Pointer, opened during Header parsing. + FILE *fp; + + /// ACR, ACR_LIBIDO, ExplicitVR, ImplicitVR, Unknown + FileType filetype; + + /// After opening the file, we read HEADER_LENGTH_TO_READ bytes. + static const unsigned int HEADER_LENGTH_TO_READ; + + /// \brief Elements whose value is longer than MAX_SIZE_LOAD_ELEMENT_VALUE + /// are NOT loaded. + static const unsigned int MAX_SIZE_LOAD_ELEMENT_VALUE; + + /// \brief Elements whose value is longer than MAX_SIZE_PRINT_ELEMENT_VALUE + /// are NOT printed. + static const unsigned int MAX_SIZE_PRINT_ELEMENT_VALUE; + + /// Hash Table (multimap), to provide fast access + TagDocEntryHT tagHT; + + /// Chained list, to keep the 'spacial' ordering + ListTag listEntries; + + /// Will be set 1 if user asks to 'go inside' the 'sequences' (VR = "SQ") + int enableSequences; + + /// \brief Amount of printed details for each Header Entry (Dicom Element): + /// 0 : stands for the least detail level. + int printLevel; + +public: + + +// Print + /// Canonical Printing method (see also gdcmDocument::SetPrintLevel) + virtual void Print (std::ostream &os = std::cout) + {PrintEntry(os);}; + virtual void PrintEntry (std::ostream &os = std::cout); + + // the 2 following will be merged + virtual void PrintPubDict (std::ostream &os = std::cout); + virtual void PrintShaDict (std::ostream &os = std::cout); + +// Dictionnaries + gdcmDict *GetPubDict(void); + gdcmDict *GetShaDict(void); + bool SetShaDict(gdcmDict *dict); + bool SetShaDict(DictKey dictName); + +// Informations contained in the parser + virtual bool IsReadable(void); + bool IsImplicitVRLittleEndianTransferSyntax(void); + bool IsExplicitVRLittleEndianTransferSyntax(void); + bool IsDeflatedExplicitVRLittleEndianTransferSyntax(void); + bool IsExplicitVRBigEndianTransferSyntax(void); + FileType GetFileType(void); + +// Read (used in gdcmFile, gdcmDicomDir) + FILE *OpenFile(bool exception_on_error = false) throw(gdcmFileError); + bool CloseFile(void); + +// Write (used in gdcmFile, gdcmDicomDir) + virtual bool Write(FILE *, FileType); + virtual void WriteEntryTagVRLength(gdcmDocEntry *tag, + FILE *_fp, FileType type); + virtual void WriteEntryValue(gdcmDocEntry *tag,FILE *_fp,FileType type); + virtual bool WriteEntry(gdcmDocEntry *tag,FILE *_fp,FileType type); + virtual bool WriteEntries(FILE *_fp,FileType type); + void WriteEntriesDeprecated(FILE *_fp,FileType type); // JPR + + gdcmDocEntry * ReplaceOrCreateByNumber(std::string Value, + guint16 Group, guint16 Elem); + bool ReplaceIfExistByNumber (char *Value, guint16 Group, guint16 Elem); + +// System access + guint16 SwapShort(guint16); // needed by gdcmFile + guint32 SwapLong(guint32); // needed by gdcmFile + guint16 UnswapShort(guint16); // needed by gdcmFile + guint32 UnswapLong(guint32); // needed by gdcmFile + +protected: + // Constructor and destructor are protected to forbid end user + // to instanciate from this class gdcmDocument (only gdcmHeader and + // gdcmDicomDir are meaningfull). + gdcmDocument(bool exception_on_error = false); + gdcmDocument(const char *inFilename, + bool exception_on_error = false, + bool enable_sequences = false, + bool ignore_shadow = false); + virtual ~gdcmDocument(void); +// Entry + int CheckIfEntryExistByNumber(guint16 Group, guint16 Elem ); // int ! + virtual std::string GetEntryByName (std::string tagName); + virtual std::string GetEntryVRByName (std::string tagName); + virtual std::string GetEntryByNumber (guint16 group, guint16 element); + virtual std::string GetEntryVRByNumber(guint16 group, guint16 element); + virtual int GetEntryLengthByNumber(guint16 group, guint16 element); + + virtual bool SetEntryByName (std::string content, std::string tagName); + virtual bool SetEntryByNumber(std::string content, + guint16 group, guint16 element); + virtual bool SetEntryLengthByNumber(guint32 length, + guint16 group, guint16 element); + + virtual size_t GetEntryOffsetByNumber (guint16 Group, guint16 Elem); + virtual void *GetEntryVoidAreaByNumber(guint16 Group, guint16 Elem); + virtual void *LoadEntryVoidArea (guint16 Group, guint16 Element); + virtual bool SetEntryVoidAreaByNumber(void *a, guint16 Group, guint16 Elem); + + virtual void UpdateShaEntries(void); + +// Header entry + gdcmDocEntry *GetDocEntryByNumber (guint16 group, guint16 element); + gdcmDocEntry *GetDocEntryByName (std::string Name); + IterHT GetDocEntrySameNumber(guint16 group, guint16 element); +// IterHT GetDocEntrySameName (std::string Name); + + void LoadDocEntrySafe(gdcmDocEntry *); + + void UpdateGroupLength(bool SkipSequence = false, + FileType type = ImplicitVR); + + void AddDocEntry (gdcmDocEntry *); + + +private: + // Read + bool LoadDocEntries(bool exception_on_error = false) throw(gdcmFormatError); + + void LoadDocEntry (gdcmDocEntry *); + void FindDocEntryLength(gdcmDocEntry *); + void FindDocEntryVR (gdcmDocEntry *); + bool CheckDocEntryVR (gdcmDocEntry *, VRKey); + + std::string GetDocEntryValue (gdcmDocEntry *); + std::string GetDocEntryUnvalue(gdcmDocEntry *); + + void SkipDocEntry (gdcmDocEntry *); + void FixDocEntryFoundLength(gdcmDocEntry *, guint32); + bool IsDocEntryAnInteger (gdcmDocEntry *); + + guint32 FindDocEntryLengthOB(void); + + guint16 ReadInt16(void); + guint32 ReadInt32(void); + void SkipBytes(guint32); + + void Initialise(void); + bool CheckSwap(void); + void SwitchSwapToBigEndian(void); + void SetMaxSizeLoadEntry(long); + void SetMaxSizePrintEntry(long); + + // DictEntry related utilities + gdcmDictEntry *GetDictEntryByName (std::string Name); + gdcmDictEntry *GetDictEntryByNumber(guint16, guint16); + gdcmDictEntry *NewVirtualDictEntry(guint16 group, + guint16 element, + std::string vr = "unkn", + std::string fourth = "unkn", + std::string name = "unkn"); + //gdcmDictEntry *NewVirtualDictEntry(gdcmDocEntry *); // never defined + + // DocEntry related utilities + + gdcmDocEntry *ReadNextDocEntry (void); + gdcmDocEntry *NewDocEntryByNumber(guint16 group, + guint16 element); + gdcmDocEntry *NewDocEntryByName (std::string Name); + + // Deprecated (Not used) --> commented out + //gdcmDocEntry *NewManualDocEntryToPubDict(std::string NewTagName, + // std::string VR); + + guint32 GenerateFreeTagKeyInGroup(guint16 group); + +public: +// Accessors: + /// Accessor to \ref printLevel + void SetPrintLevel(int level) { printLevel = level; }; + + /// Accessor to \ref filename + inline std::string GetFileName(void) {return filename;} + + /// Accessor to \ref filename + inline void SetFileName(char* fileName) {filename = fileName;} + + /// Accessor to \ref gdcmDocument::tagHT + inline TagDocEntryHT &GetEntry(void) { return tagHT; }; + + /// Accessor to \ref gdcmDocument::listEntries + inline ListTag &GetListEntry(void) { return listEntries; }; + + /// 'Swap code' accessor (see \ref sw ) + inline int GetSwapCode(void) { return sw; } +}; + +//----------------------------------------------------------------------------- +#endif diff --git a/src/gdcmElementSet.cxx b/src/gdcmElementSet.cxx new file mode 100644 index 00000000..33325c3f --- /dev/null +++ b/src/gdcmElementSet.cxx @@ -0,0 +1,152 @@ +// gdcmElementSet.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmElementSet.h" +#include "gdcmTS.h" + + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmElementSet + * \brief Constructor from a given gdcmElementSet + */ +gdcmDocEntrySet::gdcmDocEntrySet() { + +} + +/** + * \ingroup gdcmElementSet + * \brief Canonical destructor. + */ +gdcmElementSet::~gdcmElementSet() +{ +/* for(tous les DocEntry) + { + delete *cc; + } + */ +} + + +//----------------------------------------------------------------------------- +// Public + + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +/** + * \brief Find the value Length of the passed Header Entry + * @param Entry Header Entry whose length of the value shall be loaded. + */ + void gdcmElementSet::FindDocEntryLength (gdcmDocEntry *Entry) { + guint16 element = Entry->GetElement(); + //guint16 group = Entry->GetGroup(); //FIXME + std::string vr = Entry->GetVR(); + guint16 length16; + + + if ( (filetype == ExplicitVR) && (! Entry->IsImplicitVR()) ) + { + if ( (vr=="OB") || (vr=="OW") || (vr=="SQ") || (vr=="UN") ) + { + // The following reserved two bytes (see PS 3.5-2001, section + // 7.1.2 Data element structure with explicit vr p27) must be + // skipped before proceeding on reading the length on 4 bytes. + fseek(fp, 2L, SEEK_CUR); + guint32 length32 = ReadInt32(); + + if ( (vr == "OB") && (length32 == 0xffffffff) ) + { + Entry->SetLength(FindHeaderEntryLengthOB()); + return; + } + FixHeaderEntryFoundLength(Entry, length32); + return; + } + + // Length is encoded on 2 bytes. + length16 = ReadInt16(); + + // We can tell the current file is encoded in big endian (like + // Data/US-RGB-8-epicard) when we find the "Transfer Syntax" tag + // and it's value is the one of the encoding of a big endian file. + // In order to deal with such big endian encoded files, we have + // (at least) two strategies: + // * when we load the "Transfer Syntax" tag with value of big endian + // encoding, we raise the proper flags. Then we wait for the end + // of the META group (0x0002) among which is "Transfer Syntax", + // before switching the swap code to big endian. We have to postpone + // the switching of the swap code since the META group is fully encoded + // in little endian, and big endian coding only starts at the next + // group. The corresponding code can be hard to analyse and adds + // many additional unnecessary tests for regular tags. + // * the second strategy consists in waiting for trouble, that shall + // appear when we find the first group with big endian encoding. This + // is easy to detect since the length of a "Group Length" tag (the + // ones with zero as element number) has to be of 4 (0x0004). When we + // encounter 1024 (0x0400) chances are the encoding changed and we + // found a group with big endian encoding. + // We shall use this second strategy. In order to make sure that we + // can interpret the presence of an apparently big endian encoded + // length of a "Group Length" without committing a big mistake, we + // add an additional check: we look in the already parsed elements + // for the presence of a "Transfer Syntax" whose value has to be "big + // endian encoding". When this is the case, chances are we have got our + // hands on a big endian encoded file: we switch the swap code to + // big endian and proceed... + if ( (element == 0x0000) && (length16 == 0x0400) ) + { + if ( ! IsExplicitVRBigEndianTransferSyntax() ) + { + dbg.Verbose(0, "gdcmDocument::FindLength", "not explicit VR"); + errno = 1; + return; + } + length16 = 4; + SwitchSwapToBigEndian(); + // Restore the unproperly loaded values i.e. the group, the element + // and the dictionary entry depending on them. + guint16 CorrectGroup = SwapShort(Entry->GetGroup()); + guint16 CorrectElem = SwapShort(Entry->GetElement()); + gdcmDictEntry * NewTag = GetDictEntryByNumber(CorrectGroup, + CorrectElem); + if (!NewTag) + { + // This correct tag is not in the dictionary. Create a new one. + NewTag = NewVirtualDictEntry(CorrectGroup, CorrectElem); + } + // FIXME this can create a memory leaks on the old entry that be + // left unreferenced. + Entry->SetDictEntry(NewTag); + } + + // Heuristic: well some files are really ill-formed. + if ( length16 == 0xffff) + { + length16 = 0; + //dbg.Verbose(0, "gdcmDocument::FindLength", + // "Erroneous element length fixed."); + // Actually, length= 0xffff means that we deal with + // Unknown Sequence Length + } + FixHeaderEntryFoundLength(Entry, (guint32)length16); + return; + } + else + { + // Either implicit VR or a non DICOM conformal (see note below) explicit + // VR that ommited the VR of (at least) this element. Farts happen. + // [Note: according to the part 5, PS 3.5-2001, section 7.1 p25 + // on Data elements "Implicit and Explicit VR Data Elements shall + // not coexist in a Data Set and Data Sets nested within it".] + // Length is on 4 bytes. + + FixHeaderEntryFoundLength(Entry, ReadInt32()); + return; + } +} diff --git a/src/gdcmElementSet.h b/src/gdcmElementSet.h new file mode 100644 index 00000000..7d7dd19a --- /dev/null +++ b/src/gdcmElementSet.h @@ -0,0 +1,21 @@ +// gdcmElementSet.h + +#ifndef GDCMELEMENTSET_H +#define GDCMELEMENTSET_H + +//----------------------------------------------------------------------------- + +class GDCM_EXPORT gdcmElementSet : public gdcmDocEntrySet +{ +public: + +protected: + +private: + +}; + + +//----------------------------------------------------------------------------- +#endif + diff --git a/src/gdcmSQItem.cxx b/src/gdcmSQItem.cxx new file mode 100644 index 00000000..8b77a1c2 --- /dev/null +++ b/src/gdcmSQItem.cxx @@ -0,0 +1,41 @@ +// gdcmSQItem.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmSQItem.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" + + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmSQItem + * \brief Constructor from a given gdcmSQItem + * @param in Pointer to existing dictionary entry + */ +gdcmSQItem::gdcmSQItem() : gdcmDocEntry( ) { + + +} + + + + +//----------------------------------------------------------------------------- +// Print +/* + * \ingroup gdcmSQItem + * \brief canonical Printer + */ + + +//----------------------------------------------------------------------------- +// Public + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +//----------------------------------------------------------------------------- diff --git a/src/gdcmSQItem.h b/src/gdcmSQItem.h new file mode 100644 index 00000000..8ef543f3 --- /dev/null +++ b/src/gdcmSQItem.h @@ -0,0 +1,40 @@ +// gdcmSQItem.h + +#ifndef GDCMSQITEM_H +#define GDCMSQITEM_H + +//----------------------------------------------------------------------------- +typedef std::list ListDocEntry; +//----------------------------------------------------------------------------- + +class GDCM_EXPORT gdcmSQItem : public gdcmDocEntrySet +{ +public: + gdcmSQItem(void); + ~gdcmSQItem(void); + + virtual void Print(std::ostream &os = std::cout); + + /// \brief returns the DocEntry chained List for this SQ Item. + inline ListDocEntry &GetDocEntries() + {return docEntries;}; + + /// \brief adds the passed DocEntry to the DocEntry chained List for this SQ Item. + inline void AddDocEntry(DocEntry *e) + {docEntries.push_back(e);}; + +protected: + +private: + + +// Variables + +/// \brief chained list of (Elementary) Doc Entries + ListDocEntry docEntries; +}; + + +//----------------------------------------------------------------------------- +#endif + diff --git a/src/gdcmSeqEntry.cxx b/src/gdcmSeqEntry.cxx new file mode 100644 index 00000000..8f70af99 --- /dev/null +++ b/src/gdcmSeqEntry.cxx @@ -0,0 +1,53 @@ +// gdcmSeqEntry.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmSeqEntry.h" +#include "gdcmTS.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" + + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmSeqEntry + * \brief Constructor from a given gdcmSeqEntry + * @param in Pointer to existing dictionary entry + */ +gdcmSeqEntry::gdcmSeqEntry() : gdcmDocEntry( ) { + + +} + + +/** + * \ingroup gdcmSeqEntry + * \brief Canonical destructor. + */ +gdcmSeqEntry::~gdcmSeqEntry() +{ +/* for(tous les Items) + { + delete *cc; + } + */ +} + +//----------------------------------------------------------------------------- +// Print +/* + * \ingroup gdcmSeqEntry + * \brief canonical Printer + */ + + +//----------------------------------------------------------------------------- +// Public + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +//----------------------------------------------------------------------------- diff --git a/src/gdcmSeqEntry.h b/src/gdcmSeqEntry.h new file mode 100644 index 00000000..7a818ef0 --- /dev/null +++ b/src/gdcmSeqEntry.h @@ -0,0 +1,43 @@ +// gdcmSeqEntry.h + +#ifndef GDCMSQDOCENTRY_H +#define GDCMSQDOCENTRY_H + +//----------------------------------------------------------------------------- +typedef std::list ListSQItem; +//----------------------------------------------------------------------------- + +class GDCM_EXPORT gdcmSeqEntry : public gdcmDocEntry +{ +public: + gdcmSeqEntry(void); + ~gdcmSeqEntry(void); + + virtual void Print(std::ostream &os = std::cout); + + /// \brief returns the SQITEM chained List for this SeQuence. + inline ListSQItem &GetSQItems() + {return items;}; + + /// \brief adds the passed ITEM to the ITEM chained List for this SeQuence. + inline void AddSQItem(gdcmSQItem *it) + {items.push_back(it);}; + + /// \brief creates a new SQITEM for this SeQuence. + gdcmSQItem * NewItem(void); + +protected: + +private: + +// Variables + +/// \brief chained list of SQ Items + ListSQItem items; + +}; + + +//----------------------------------------------------------------------------- +#endif + diff --git a/src/gdcmValEntry.cxx b/src/gdcmValEntry.cxx new file mode 100644 index 00000000..e1dad038 --- /dev/null +++ b/src/gdcmValEntry.cxx @@ -0,0 +1,42 @@ +// gdcmValEntry.cxx +//----------------------------------------------------------------------------- +// +#include "gdcmValEntry.h" +#include "gdcmTS.h" +#include "gdcmGlobal.h" +#include "gdcmUtil.h" + + +//----------------------------------------------------------------------------- +// Constructor / Destructor +/** + * \ingroup gdcmValEntry + * \brief Constructor from a given gdcmDictEntry + * @param in Pointer to existing dictionary entry + */ +gdcmValEntry::gdcmValEntry() : gdcmDocEntry( ) { + + +} + + + + +//----------------------------------------------------------------------------- +// Print +/* + * \ingroup gdcmDocEntry + * \brief canonical Printer + */ + + +//----------------------------------------------------------------------------- +// Public + +//----------------------------------------------------------------------------- +// Protected + +//----------------------------------------------------------------------------- +// Private + +//----------------------------------------------------------------------------- diff --git a/src/gdcmValEntry.h b/src/gdcmValEntry.h new file mode 100644 index 00000000..b40401fa --- /dev/null +++ b/src/gdcmValEntry.h @@ -0,0 +1,40 @@ +// gdcmValEntry.h +//----------------------------------------------------------------------------- +#ifndef GDCMValEntry_H +#define GDCMValEntry_H + +#include +#include + +//----------------------------------------------------------------------------- +/** + * \ingroup gdcmBinEntry + * \brief The dicom header of a Dicom file contains a set of such entries + * (when successfuly parsed against a given Dicom dictionary) + * This one contains a 'non string' value + */ +class GDCM_EXPORT gdcmDocEntry : public gdcmValEntry { + +public: + +protected: + +private: + +// Variables + + /// \brief Header Entry value, stores as a std::string (VR will be used, + /// later, to decode) + std::string value; + + + + + + + + + +//----------------------------------------------------------------------------- +#endif + -- 2.48.1