10 #include <netinet/in.h>
16 #define HEADER_LENGHT_TO_READ 256 // on ne lit plus que le debut
18 //FIXME: this looks dirty to me...
19 #define str2num(str, typeNum) *((typeNum *)(str))
21 VRHT * gdcmHeader::dicom_vr = (VRHT*)0;
22 gdcmDictSet* gdcmHeader::Dicts = new gdcmDictSet();
24 void gdcmHeader::Initialise(void) {
25 if (!gdcmHeader::dicom_vr)
27 PixelPosition = (size_t)0;
28 PixelsTrouves = false;
29 RefPubDict = gdcmHeader::Dicts->GetDefaultPublicDict();
30 RefShaDict = (gdcmDict*)0;
33 gdcmHeader::gdcmHeader (char* InFilename) {
34 filename = InFilename;
36 fp=fopen(InFilename,"rw");
37 dbg.Error(!fp, "gdcmHeader::gdcmHeader cannot open file", InFilename);
42 gdcmHeader::~gdcmHeader (void) {
46 void gdcmHeader::InitVRDict (void) {
48 dbg.Verbose(0, "gdcmHeader::InitVRDict:", "VR dictionary allready set");
52 (*vr)["AE"] = "Application Entity"; // 16 car max
53 (*vr)["AS"] = "Age String"; // 4 car fixe
54 (*vr)["AT"] = "Attribute Tag"; // 2 unsigned short int
55 (*vr)["CS"] = "Code String"; // 16 car max
56 (*vr)["DA"] = "Date"; // 8 car fixe
57 (*vr)["DS"] = "Decimal String"; // Decimal codé Binaire 16 max
58 (*vr)["DT"] = "Date Time"; // 26 car max
59 (*vr)["FL"] = "Floating Point Single"; // 4 octets IEEE 754:1985
60 (*vr)["FD"] = "Floating Point Double"; // 8 octets IEEE 754:1985
61 (*vr)["IS"] = "Integer String"; // en format externe 12 max
62 (*vr)["LO"] = "Long String"; // 64 octets max
63 (*vr)["LT"] = "Long Text"; // 10240 max
64 (*vr)["OB"] = "Other Byte String";
65 (*vr)["OW"] = "Other Word String";
66 (*vr)["PN"] = "Person Name";
67 (*vr)["SH"] = "Short String"; // 16 car max
68 (*vr)["SL"] = "Signed Long";
69 (*vr)["SQ"] = "Sequence of Items"; // Not Applicable
70 (*vr)["SS"] = "Signed Short"; // 2 octets
71 (*vr)["ST"] = "Short Text"; // 1024 car max
72 (*vr)["TM"] = "Time"; // 16 car max
73 (*vr)["UI"] = "Unique Identifier"; // 64 car max
74 (*vr)["UN"] = "Unknown";
75 (*vr)["UT"] = "Unlimited Text"; // 2 puissance 32 -1 car max
76 (*vr)["UL"] = "Unsigned Long "; // 4 octets fixe
77 (*vr)["US"] = "Unsigned Short "; // 2 octets fixe
83 * \brief La seule maniere sure que l'on aie pour determiner
84 * si on est en LITTLE_ENDIAN, BIG-ENDIAN,
85 * BAD-LITTLE-ENDIAN, BAD-BIG-ENDIAN
86 * est de trouver l'element qui donne la longueur d'un 'GROUP'
87 * (on sait que la longueur de cet element vaut 0x00000004)
88 * et de regarder comment cette longueur est codee en memoire
90 * Le probleme vient de ce que parfois, il n'y en a pas ...
92 * On fait alors le pari qu'on a a faire a du LITTLE_ENDIAN propre.
93 * (Ce qui est la norme -pas respectee- depuis ACR-NEMA)
94 * Si ce n'est pas le cas, on ne peut rien faire.
96 * (il faudrait avoir des fonctions auxquelles
97 * on passe le code Swap en parametre, pour faire des essais 'manuels')
99 void gdcmHeader::CheckSwap()
102 guint32 x=4; // x : pour ntohs
103 bool net2host; // true when HostByteOrder is the same as NetworkByteOrder
107 char deb[HEADER_LENGHT_TO_READ];
109 // First, compare HostByteOrder and NetworkByteOrder in order to
110 // determine if we shall need to swap bytes (i.e. the Endian type).
116 // The easiest case is the one of a DICOM header, since it possesses a
117 // file preamble where it suffice to look for the sting "DICM".
118 lgrLue = fread(deb, 1, HEADER_LENGHT_TO_READ, fp);
121 if(memcmp(entCur, "DICM", (size_t)4) == 0) {
122 filetype = TrueDicom;
123 dbg.Verbose(0, "gdcmHeader::CheckSwap:", "looks like DICOM Version3");
126 dbg.Verbose(0, "gdcmHeader::CheckSwap:", "not a DICOM Version3 file");
129 if(filetype == TrueDicom) {
130 // Next, determine the value representation (VR). Let's skip to the
131 // first element (0002, 0000) and check there if we find "UL", in
132 // which case we (almost) know it is explicit VR.
133 // WARNING: if it happens to be implicit VR then what we will read
134 // is the length of the group. If this ascii representation of this
135 // length happens to be "UL" then we shall believe it is explicit VR.
136 // FIXME: in order to fix the above warning, we could read the next
137 // element value (or a couple of elements values) in order to make
138 // sure we are not commiting a big mistake.
140 // * the 128 bytes of File Preamble (often padded with zeroes),
141 // * the 4 bytes of "DICM" string,
142 // * the 4 bytes of the first tag (0002, 0000),
143 // i.e. a total of 136 bytes.
145 if(memcmp(entCur, "UL", (size_t)2) == 0) {
146 filetype = ExplicitVR;
147 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
148 "explicit Value Representation");
150 filetype = ImplicitVR;
151 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
152 "not an explicit Value Representation");
157 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
158 "HostByteOrder != NetworkByteOrder");
161 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
162 "HostByteOrder = NetworkByteOrder");
165 // Position the file position indicator at first tag (i.e.
166 // after the file preamble and the "DICM" string).
168 fseek (fp, 132L, SEEK_SET);
170 } // End of TrueDicom
172 // Alas, this is not a DicomV3 file and whatever happens there is no file
173 // preamble. We can reset the file position indicator to where the data
174 // is (i.e. the beginning of the file).
177 // Our next best chance would be to be considering a 'clean' ACR/NEMA file.
178 // By clean we mean that the length of the first tag is written down.
179 // If this is the case and since the length of the first group HAS to be
180 // four (bytes), then determining the proper swap code is straightforward.
183 s = str2num(entCur, int);
203 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
204 "ACE/NEMA unfound swap info (time to raise bets)");
207 // We are out of luck. It is not a DicomV3 nor a 'clean' ACR/NEMA file.
208 // It is time for despaired wild guesses. So, let's assume this file
209 // happens to be 'dirty' ACR/NEMA, i.e. the length of the group it
210 // not present. Then the only info we have is the net2host one.
211 //FIXME Si c'est du RAW, ca degagera + tard
221 * \ingroup gdcmHeader
222 * \brief recupere la longueur d'un champ DICOM.
224 * 1/ le fichier doit deja avoir ete ouvert,
225 * 2/ CheckSwap() doit avoir ete appele
226 * 3/ la partie 'group' ainsi que la partie 'elem'
227 * de l'acr_element doivent avoir ete lues.
229 * ACR-NEMA : we allways get
230 * GroupNumber (2 Octets)
231 * ElementNumber (2 Octets)
232 * ElementSize (4 Octets)
233 * DICOM en implicit Value Representation :
234 * GroupNumber (2 Octets)
235 * ElementNumber (2 Octets)
236 * ElementSize (4 Octets)
238 * DICOM en explicit Value Representation :
239 * GroupNumber (2 Octets)
240 * ElementNumber (2 Octets)
241 * ValueRepresentation (2 Octets)
242 * ElementSize (2 Octets)
244 * ATTENTION : dans le cas ou ValueRepresentation = OB, OW, SQ, UN
245 * GroupNumber (2 Octets)
246 * ElementNumber (2 Octets)
247 * ValueRepresentation (2 Octets)
248 * zone reservee (2 Octets)
249 * ElementSize (4 Octets)
251 * @param sw code swap
252 * @param skippedLength pointeur sur nombre d'octets que l'on a saute qd
253 * la lecture est finie
254 * @param longueurLue pointeur sur longueur (en nombre d'octets)
256 * @return longueur retenue pour le champ
259 void gdcmHeader::FindVR( ElValue *pleCourant) {
262 long PositionOnEntry = ftell(fp);
264 if (filetype != ExplicitVR)
267 lgrLue=fread (&VR, (size_t)2,(size_t)1, fp);
270 // Warning: we believe this is explicit VR (Value Representation) because
271 // we used a heuristic that found "UL" in the first tag. Alas this
272 // doesn't guarantee that all the tags will be in explicit VR. In some
273 // cases (see e-film filtered files) one finds implicit VR tags mixed
274 // within an explicit VR file. Hence we make sure the present tag
275 // is in explicit VR and try to fix things if it happens not to be
278 // FIXME There should be only one occurence returned. Avoid the
279 // first extraction by calling proper method.
280 VRAtr FoundVR = dicom_vr->find(string(VR))->first;
281 if ( ! FoundVR.empty()) {
282 pleCourant->SetVR(FoundVR);
286 // We thought this was explicit VR, but we end up with an
287 // implicit VR tag. Let's backtrack.
288 pleCourant->SetVR("Implicit");
289 fseek(fp, PositionOnEntry, SEEK_SET);
292 void gdcmHeader::RecupLgr( ElValue *pleCourant) {
295 unsigned short int l_gr_2;
297 string vr = pleCourant->GetVR();
299 if ( (filetype == ExplicitVR) && (vr != "Implicit") ) {
300 if ( ( vr == "OB" ) || ( vr == "OW" )
301 || ( vr == "SQ" ) || ( vr == "UN" ) ) {
303 // The following two bytes are reserved, so we skip them,
304 // and we proceed on reading the length on 4 bytes.
305 fseek(fp, 2L,SEEK_CUR);
306 lgrLue=fread (&l_gr, (size_t)4,(size_t)1, fp);
307 l_gr = SwapLong((guint32)l_gr);
310 //on lit la lgr sur DEUX octets
311 lgrLue=fread (&l_gr_2, (size_t)2,(size_t)1, fp);
313 l_gr_2 = SwapShort((unsigned short)l_gr_2);
315 if ( l_gr_2 == 0xffff) {
323 //on lit la lgr sur QUATRE octets
324 lgrLue=fread (&l_gr, (size_t)4,(size_t)1, fp);
325 l_gr= SwapLong((long)l_gr);
328 // Traitement des curiosites sur la longueur
329 if ( (int)l_gr == 0xffffffff)
332 pleCourant->SetLgrElem(l_gr);
336 * \ingroup gdcmHeader
337 * \brief remet les octets dans un ordre compatible avec celui du processeur
339 * @return longueur retenue pour le champ
341 guint32 gdcmHeader::SwapLong(guint32 a) {
342 // FIXME: il pourrait y avoir un pb pour les entiers negatifs ...
347 a=( ((a<<24) & 0xff000000) | ((a<<8) & 0x00ff0000) |
348 ((a>>8) & 0x0000ff00) | ((a>>24) & 0x000000ff) );
352 a=( ((a<<16) & 0xffff0000) | ((a>>16) & 0x0000ffff) );
356 a=( ((a<<8) & 0xff00ff00) | ((a>>8) & 0x00ff00ff) );
359 dbg.Error(" gdcmHeader::SwapLong : unset swap code");
366 * \ingroup gdcmHeader
367 * \brief remet les octets dans un ordre compatible avec celui du processeur
369 * @return longueur retenue pour le champ
371 short int gdcmHeader::SwapShort(short int a) {
372 if ( (sw==4321) || (sw==2143) )
373 a =(((a<<8) & 0x0ff00) | ((a>>8)&0x00ff));
378 * \ingroup gdcmHeader
379 * \brief lit le dicom_element suivant.
380 * (le fichier doit deja avoir ete ouvert,
381 * _IdAcrCheckSwap(ID_DCM_HDR *e) avoir ete appele)
382 * @param e ID_DCM_HDR dans lequel effectuer la recherche.
383 * @param sw code swap.
384 * @return En cas de succes, 1
388 ElValue * gdcmHeader::ReadNextElement(void) {
393 ElValue * nouvDcmElem;
395 // ------------------------- Lecture Num group : g
396 lgrLue=fread (&g, (size_t)2,(size_t)1, fp);
399 dbg.Verbose(1, "ReadNextElement: EOF encountered");
403 dbg.Verbose(1, "ReadNextElement: failed to read NumGr");
407 if (sw) g= SwapShort(((short)g));
409 // ------------------------- Lecture Num Elem : n
410 lgrLue=fread (&n, (size_t)2,(size_t)1, fp);
413 dbg.Verbose(1, "ReadNextElement: EOF encountered");
417 dbg.Verbose(1, "ReadNextElement: failed to read NumElem");
421 if(sw) n= SwapShort(((short)n));
423 // Find out if the tag we encountered is in the dictionaries:
424 gdcmDictEntry * NewTag = IsInDicts(g, n);
426 NewTag = new gdcmDictEntry(g, n, "Unknown", "Unknown", "Unkown");
428 nouvDcmElem = new ElValue(NewTag);
430 dbg.Verbose(1, "ReadNextElement: failed to allocate ElValue");
434 // ------------------------- Lecture longueur element : l
437 RecupLgr(nouvDcmElem);
438 nouvDcmElem->SetOffset(ftell(fp));
439 l = nouvDcmElem->GetLgrElem();
441 //FIXMEif(!memcmp( VR,"SQ",(size_t)2 )) { // ca annonce une SEQUENCE d'items ?!
442 //FIXME l_gr=0; // on lira donc les items de la sequence
447 if(g==0xfffe) l=0; // pour sauter les indicateurs de 'SQ'
450 // ------------------------- Lecture Valeur element
452 // FIXME The exact size should be l if we move to strings or whatever
453 // CLEAN ME NEWValue used to be nouvDcmElem->valeurElem
454 char* NewValue = (char*)g_malloc(l+1);
461 // FIXME les elements trop long (seuil a fixer a la main) ne devraient
462 // pas etre charge's !!!! Voir TODO.
463 lgrLue=fread (NewValue, (size_t)l,(size_t)1, fp);
466 // ------------------------- Doit-on le Swapper ?
468 if ((n==0) && sw) { // n=0 : lgr du groupe : guint32
469 *(guint32 *) NewValue = SwapLong ((*(guint32 *) NewValue));
472 if ( (g/2)*2-g==0) { /* on ne teste pas les groupes impairs */
474 if ((l==4)||(l==2)) { // pour eviter de swapper les chaines
477 // FIXME make reference to nouvDcmElem->GetTag
478 string VR = NewTag->GetVR();
479 if ( (VR == "UL") || (VR == "US")
480 || (VR == "SL") || (VR == "SS")
481 || (g == 0x0028 && ( n == 0x0005 || n == 0x0200) )) {
482 // seuls (28,5) de vr RET et (28,200) sont des entiers
483 // ... jusqu'a preuve du contraire
486 *(guint32 *) NewValue =
487 SwapLong ((*(guint32 *) NewValue));
490 *(unsigned short *) NewValue =
491 SwapShort ((*(unsigned short *)NewValue));
494 } /* fin if l==2 ==4 */
495 } /* fin if g pair */
498 nouvDcmElem->value = NewValue;
503 * \ingroup gdcmHeader
504 * \brief If we encountered the offset of the pixels in the file
505 * (Pixel Data) then keep the info aside.
507 void gdcmHeader::SetAsidePixelData(ElValue* elem) {
508 /// FIXME this wall process is bizarre:
509 // on peut pas lire pixel data et pixel location puis
510 // a la fin de la lecture aller interpreter ce qui existe ?
511 // penser a nettoyer les variables globales associes genre
512 // PixelsTrouve ou grPixelTrouve...
514 // They are two cases :
515 // * the pixel data (i.e. the image or the volume) is pointed by it's
516 // default official tag (0x7fe0,0x0010),
517 // * the writer of this file decided to put the image "address" (i.e the
518 // offset from the begining of the file) at a different tag.
519 // Then the "Pixel Data" offset might be found by indirection through
520 // the "Image Location" tag (0x0028, 0x0200). In other terms the Image
521 // Location tag contains the group where the "Pixel Data" offset is and
522 // inside this group the element is conventionally at element 0x0010
523 // (when the norm is respected).
525 // Hence getting our hands on the Pixel Data is a two stage process:
526 // 1/ * find if the "Pixel Data" tag exists.
527 // * if it does not exist, look for the "Pixel Location" tag.
528 // 2/ look at the proper tag ("Pixel Data" or "Pixel Location" when
529 // it exists) what the offset is.
532 g = elem->GetGroup();
533 n = elem->GetElement();
534 if (!grPixelTrouve) { // on n a pas encore trouve les pixels
536 if (n > 0x0200 || g == 0x7FE0 ) { // on a depasse (28,200)
539 grPixelTrouve = true;
541 } else { // on est sur (28,200)
545 char* NewValue = (char*)g_malloc(elem->GetLgrElem()+1);
546 // FIXME: not very elegant conversion
548 *((char*)(&grPixel)+i) = *(NewValue+i);
549 elem->SetValue(NewValue);
551 if (grPixel != 0x7FE0) // Vieux pb Philips
552 numPixel = 0x1010; // encore utile ??
558 } else { // on vient de trouver les pixels
561 PixelPosition = elem->Offset;
562 PixelsTrouves = true;
563 dbg.Verbose(0, "gdcmHeader::SetAsidePixelData:",
570 gdcmDictEntry * gdcmHeader::IsInDicts(guint32 group, guint32 element) {
571 gdcmDictEntry * found = (gdcmDictEntry*)0;
572 if (!RefPubDict && !RefShaDict) {
573 //FIXME build a default dictionary !
574 printf("FIXME in gdcmHeader::IsInDicts\n");
577 found = RefPubDict->GetTag(group, element);
582 found = RefShaDict->GetTag(group, element);
589 string gdcmHeader::GetPubElValByNumber(unsigned short, unsigned short) {
593 * \ingroup gdcmHeader
594 * \brief renvoie un pointeur sur le ID_DCM_HDR correspondant au fichier
595 * @param filename Nom du fichier ACR / LibIDO / DICOM
596 * @return le ID_DCM_HDR
599 void gdcmHeader::BuildHeader(void) {
600 ElValue * newElValue = (ElValue *)0;
605 while ( (newElValue = ReadNextElement()) ) {
606 PubElVals.Add(newElValue);
610 int gdcmHeader::PrintPubElVal(ostream & os) {
611 return PubElVals.Print(os);
614 void gdcmHeader::PrintPubDict(ostream & os) {
615 RefPubDict->Print(os);