10 #include <netinet/in.h>
16 #define LGR_ENTETE_A_LIRE 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 bool grPixelTrouve = false;
28 PixelPosition = (size_t)0;
29 PixelsTrouves = false;
30 RefPubDict = gdcmHeader::Dicts->GetDefaultPublicDict();
31 RefShaDict = (gdcmDict*)0;
34 gdcmHeader::gdcmHeader (char* InFilename) {
35 filename = InFilename;
37 fp=fopen(InFilename,"rw");
38 dbg.Error(!fp, "gdcmHeader::gdcmHeader cannot open file", InFilename);
43 gdcmHeader::~gdcmHeader (void) {
47 void gdcmHeader::InitVRDict (void) {
49 dbg.Verbose(0, "gdcmHeader::InitVRDict:", "VR dictionary allready set");
53 (*vr)["AE"] = "Application Entity"; // 16 car max
54 (*vr)["AS"] = "Age String"; // 4 car fixe
55 (*vr)["AT"] = "Attribute Tag"; // 2 unsigned short int
56 (*vr)["CS"] = "Code String"; // 16 car max
57 (*vr)["DA"] = "Date"; // 8 car fixe
58 (*vr)["DS"] = "Decimal String"; // Decimal codé Binaire 16 max
59 (*vr)["DT"] = "Date Time"; // 26 car max
60 (*vr)["FL"] = "Floating Point Single"; // 4 octets IEEE 754:1985
61 (*vr)["FD"] = "Floating Point Double"; // 8 octets IEEE 754:1985
62 (*vr)["IS"] = "Integer String"; // en format externe 12 max
63 (*vr)["LO"] = "Long String"; // 64 octets max
64 (*vr)["LT"] = "Long Text"; // 10240 max
65 (*vr)["OB"] = "Other Byte String";
66 (*vr)["OW"] = "Other Word String";
67 (*vr)["PN"] = "Person Name";
68 (*vr)["SH"] = "Short String"; // 16 car max
69 (*vr)["SL"] = "Signed Long";
70 (*vr)["SQ"] = "Sequence of Items"; // Not Applicable
71 (*vr)["SS"] = "Signed Short"; // 2 octets
72 (*vr)["ST"] = "Short Text"; // 1024 car max
73 (*vr)["TM"] = "Time"; // 16 car max
74 (*vr)["UI"] = "Unique Identifier"; // 64 car max
75 (*vr)["UN"] = "Unknown";
76 (*vr)["UT"] = "Unlimited Text"; // 2 puissance 32 -1 car max
77 (*vr)["UL"] = "Unsigned Long "; // 4 octets fixe
78 (*vr)["US"] = "Unsigned Short "; // 2 octets fixe
84 * \brief La seule maniere sure que l'on aie pour determiner
85 * si on est en LITTLE_ENDIAN, BIG-ENDIAN,
86 * BAD-LITTLE-ENDIAN, BAD-BIG-ENDIAN
87 * est de trouver l'element qui donne la longueur d'un 'GROUP'
88 * (on sait que la longueur de cet element vaut 0x00000004)
89 * et de regarder comment cette longueur est codee en memoire
91 * Le probleme vient de ce que parfois, il n'y en a pas ...
93 * On fait alors le pari qu'on a a faire a du LITTLE_ENDIAN propre.
94 * (Ce qui est la norme -pas respectee- depuis ACR-NEMA)
95 * Si ce n'est pas le cas, on ne peut rien faire.
97 * (il faudrait avoir des fonctions auxquelles
98 * on passe le code Swap en parametre, pour faire des essais 'manuels')
100 void gdcmHeader::CheckSwap()
103 guint32 x=4; // x : pour ntohs
104 bool net2host; // true when HostByteOrder is the same as NetworkByteOrder
108 char deb[LGR_ENTETE_A_LIRE];
110 // On teste le processeur
117 // On commence par verifier si c'est du DICOM 'actuel'
119 lgrLue = fread(deb,1,LGR_ENTETE_A_LIRE, fp);
122 if(memcmp(entCur, "DICM", (size_t)4) == 0) {
123 filetype = TrueDicom;
124 dbg.Verbose(0, "gdcmHeader::CheckSwap:", "looks like DICOM Version3");
127 dbg.Verbose(0, "gdcmHeader::CheckSwap:", "not a DICOM Version3 file");
130 if(filetype == TrueDicom) {
131 // on saute le File Preamble (souvent a ZERO) : 128 Octets
132 // + le DICM (4), et le (0002, 0000) soit 4 (136 = 128 + 4 + 4)
134 if(memcmp(entCur, "UL", (size_t)2) == 0) {
135 // les 2 premiers octets de la lgr peuvent valoir UL --> Explicit VR
136 filetype = ExplicitVR;
137 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
138 "explicit Value Representation");
140 filetype = ImplicitVR;
141 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
142 "not an explicit Value Representation");
145 if (net2host) { // HostByteOrder is different from NetworkByteOrder
146 sw = 0; // on est sur PC ou DEC --> LITTLE-ENDIAN -> Rien a faire
147 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
148 "HostByteOrder = NetworkByteOrder");
149 } else { /* on est sur une Sun ou une SGI */
151 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
152 "HostByteOrder != NetworkByteOrder");
156 fseek (fp, 132L, SEEK_SET); //On se positionne sur le debut des info
158 } // End of TrueDicom
160 // Pas du TrueDicom : permiere hypothese c'est de l'ACR 'propre', auquel
161 // cas la lgr du premier element du groupe est FORCEMENT 4
163 s=str2num(entCur,int);
184 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
185 "unfound swap info (time to raise bets)");
188 rewind(fp); // les info commencent au debut
193 // Deuxieme hypothese : c'est de l'ACR 'pas propre' i.e. il manque
196 // On n'a pas trouve l'info de swap.
197 // Si c'est du VRAI ACR NEMA et
198 // * si on est sur une DEC ou un PC alors swap=0,
199 // * si on est sur SUN ou SGI, alors swap=4321
200 // Si c'est du RAW, ca degagera + tard
201 dbg.Verbose(0, "gdcmHeader::CheckSwap:",
202 "time for wild guesses...");
204 if (x!=ntohs(x)) // HostByteOrder is different from NetworkByteOrder
205 // on est sur PC ou DEC --> LITTLE-ENDIAN -> Rien a faire
208 // on est sur Sun ou SGI
210 rewind(fp); // les info commencent au debut
216 * \ingroup gdcmHeader
217 * \brief Pour les fichiers non TrueDicom, si le recognition
218 * code (0008,0010) s'avere etre "ACR_LIBIDO", alors
219 * valide la reconnaissance du fichier en positionnant
222 void gdcmHeader::setAcrLibido() {
225 if ( filetype != TrueDicom) {
226 printf("_setAcrLibido expects a presumably ACR file\n");
227 // Recognition Code --> n'existe plus en DICOM V3 ...
228 RecCode = GetPubElValByNumber(0x0008, 0x0010);
230 if (RecCode == "ACRNEMA_LIBIDO" ||
231 RecCode == "CANRME_AILIBOD" )
232 filetype = ACR_LIBIDO;
240 * \ingroup gdcmHeader
241 * \brief recupere la longueur d'un champ DICOM.
243 * 1/ le fichier doit deja avoir ete ouvert,
244 * 2/ CheckSwap() doit avoir ete appele
245 * 3/ la partie 'group' ainsi que la partie 'elem'
246 * de l'acr_element doivent avoir ete lues.
248 * ACR-NEMA : we allways get
249 * GroupNumber (2 Octets)
250 * ElementNumber (2 Octets)
251 * ElementSize (4 Octets)
252 * DICOM en implicit Value Representation :
253 * GroupNumber (2 Octets)
254 * ElementNumber (2 Octets)
255 * ElementSize (4 Octets)
257 * DICOM en explicit Value Representation :
258 * GroupNumber (2 Octets)
259 * ElementNumber (2 Octets)
260 * ValueRepresentation (2 Octets)
261 * ElementSize (2 Octets)
263 * ATTENTION : dans le cas ou ValueRepresentation = OB, OW, SQ, UN
264 * GroupNumber (2 Octets)
265 * ElementNumber (2 Octets)
266 * ValueRepresentation (2 Octets)
267 * zone reservee (2 Octets)
268 * ElementSize (4 Octets)
270 * @param sw code swap
271 * @param skippedLength pointeur sur nombre d'octets que l'on a saute qd
272 * la lecture est finie
273 * @param longueurLue pointeur sur longueur (en nombre d'octets)
275 * @return longueur retenue pour le champ
278 long int gdcmHeader::RecupLgr( ElValue *pleCourant, int *skippedLength) {
280 unsigned short int l_gr_2;
286 // ATTENTION : nbCode correspond au nombre d'elements dans la table
287 // de type DICOM_VR. A nettoyer.
291 if (filetype == ExplicitVR) {
292 lgrLue=fread (&VR, (size_t)2,(size_t)1, fp);
295 // Warning: we believe this is explicit VR (Value Representation) because
296 // we used a heuristic that found "UL" in the first tag. Alas this
297 // doesn't guarantee that all the tags will be in explicit VR. In some
298 // cases (see e-film filtered files) one finds implicit VR tags mixed
299 // within an explicit VR file. Hence we make sure the present tag
300 // is in explicit VR and try to fix things if it happens not to be
303 // FIXME There should be only one occurence returned. Avoid the
304 // first extraction by calling proper method.
305 VRAtr FoundVR = dicom_vr->find(string(VR))->first;
306 if (FoundVR.empty()) {
307 pleCourant->SetVR(FoundVR);
310 // On est mal : implicit VR repere
311 // mais ce n'est pas un code connu ...
312 // On reconstitue la longueur
314 dbg.Verbose(1, "gdcmHeader::RecupLgr:",
315 "Explicit VR, but no known code");
316 memcpy(&l_gr, VR,(size_t)2);
317 lgrLue=fread ( ((char*)&l_gr)+2, (size_t)2, (size_t)1, fp);
318 l_gr = SwapLong((guint32)l_gr);
319 pleCourant->SetLgrLue(l_gr);
320 if ( (int)l_gr == -1)
327 // On repart dans la sequence 'sensee'
328 if ( (!memcmp( VR,"OB",(size_t)2 )) ||
329 (!memcmp( VR,"OW",(size_t)2 )) ||
330 (!memcmp( VR,"SQ",(size_t)2 )) ||
331 (!memcmp( VR,"UN",(size_t)2 )) ) {
333 // les 2 octets suivants sont reserves: on les saute
334 fseek(fp, 2L,SEEK_CUR);
336 //on lit la lgr sur QUATRE octets
337 lgrLue=fread (&l_gr, (size_t)4,(size_t)1, fp);
338 l_gr = SwapLong((guint32)l_gr);
342 //on lit la lgr sur DEUX octets
343 lgrLue=fread (&l_gr_2, (size_t)2,(size_t)1, fp);
345 if(sw) l_gr_2 = SwapShort((unsigned short)l_gr_2);
347 pleCourant->SetLgrLue(l_gr_2);
349 if ( l_gr_2 == 0xffff) {
358 //on lit la lgr sur QUATRE octets
360 lgrLue=fread (&l_gr, (size_t)4,(size_t)1, fp);
362 l_gr= SwapLong((long)l_gr);
366 pleCourant->SetLgrLue(l_gr);
368 // Traitement des curiosites sur la longueur
370 if ( (int)l_gr == 0xffffffff)
373 if(!memcmp( VR,"SQ",(size_t)2 )) { // ca annonce une SEQUENCE d'items ?!
374 l_gr=0; // on lira donc les items de la sequence
381 * \ingroup gdcmHeader
382 * \brief remet les octets dans un ordre compatible avec celui du processeur
384 * @return longueur retenue pour le champ
386 guint32 gdcmHeader::SwapLong(guint32 a) {
387 // FIXME: il pourrait y avoir un pb pour les entiers negatifs ...
392 a=( ((a<<24) & 0xff000000) | ((a<<8) & 0x00ff0000) |
393 ((a>>8) & 0x0000ff00) | ((a>>24) & 0x000000ff) );
397 a=( ((a<<16) & 0xffff0000) | ((a>>16) & 0x0000ffff) );
401 a=( ((a<<8) & 0xff00ff00) | ((a>>8) & 0x00ff00ff) );
404 dbg.Error(" gdcmHeader::SwapLong : unset swap code");
411 * \ingroup gdcmHeader
412 * \brief remet les octets dans un ordre compatible avec celui du processeur
414 * @return longueur retenue pour le champ
416 short int gdcmHeader::SwapShort(short int a) {
417 if ( (sw==4321) || (sw==2143) )
418 a =(((a<<8) & 0x0ff00) | ((a>>8)&0x00ff));
423 * \ingroup gdcmHeader
424 * \brief lit le dicom_element suivant.
425 * (le fichier doit deja avoir ete ouvert,
426 * _IdAcrCheckSwap(ID_DCM_HDR *e) avoir ete appele)
427 * @param e ID_DCM_HDR dans lequel effectuer la recherche.
428 * @param sw code swap.
429 * @return En cas de succes, 1
433 ElValue * gdcmHeader::ReadNextElement(void) {
440 //CLEANME DICOM_ELEMENTS *t;
441 ElValue * nouvDcmElem;
443 // FIXME la probabilte pour depasser sans s'en rendre compte
444 // est grande avec le test d'egalite' suivant !
445 if(offsetCourant == taille_fich) { // On a atteint la fin du fichier
446 dbg.Verbose(1, "ReadNextElement: EOF reached");
450 // ------------------------- Lecture Num group : g
451 lgrLue=fread (&g, (size_t)2,(size_t)1, fp);
454 dbg.Verbose(1, "ReadNextElement: EOF encountered");
458 dbg.Verbose(1, "ReadNextElement: failed to read NumGr");
462 if (sw) g= SwapShort(((short)g));
464 // ------------------------- Lecture Num Elem : n
465 lgrLue=fread (&n, (size_t)2,(size_t)1, fp);
468 dbg.Verbose(1, "ReadNextElement: EOF encountered");
472 dbg.Verbose(1, "ReadNextElement: failed to read NumElem");
476 if(sw) n= SwapShort(((short)n));
478 // Find out if the tag we encountered is in the dictionaries:
479 gdcmDictEntry * NewTag = IsInDicts(g, n);
481 NewTag = new gdcmDictEntry(g, n, "Unknown", "Unknown", "Unkown");
483 nouvDcmElem = new ElValue(NewTag);
485 dbg.Verbose(1, "ReadNextElement: failed to allocate ElValue");
489 // ------------------------- Lecture longueur element : l
491 l = RecupLgr(nouvDcmElem, &skL);
493 if(g==0xfffe) l=0; // pour sauter les indicateurs de 'SQ'
495 nouvDcmElem->LgrElem=l;
497 // ------------------------- Lecture Valeur element
499 // FIXME The exact size should be l if we move to strings or whatever
500 // CLEAN ME NEWValue used to be nouvDcmElem->valeurElem
501 char* NewValue = (char*)g_malloc(l+1);
508 // FIXME les elements trop long (seuil a fixer a la main) ne devraient
509 // pas etre charge's !!!! Voir TODO.
510 lgrLue=fread (NewValue, (size_t)l,(size_t)1, fp);
512 offsetCourant += 2 + 2 + skL; // gr + num + lgr
513 nouvDcmElem->Offset = offsetCourant;
514 offsetCourant += l; // debut elem suivant
516 // ------------------------- Doit-on le Swapper ?
518 if ((n==0) && sw) { // n=0 : lgr du groupe : guint32
519 *(guint32 *) NewValue = SwapLong ((*(guint32 *) NewValue));
522 if ( (g/2)*2-g==0) { /* on ne teste pas les groupes impairs */
524 if ((l==4)||(l==2)) { // pour eviter de swapper les chaines
527 // FIXME make reference to nouvDcmElem->GetTag
528 string VR = NewTag->GetVR();
529 if ( (VR == "UL") || (VR == "US")
530 || (VR == "SL") || (VR == "SS")
531 || (g == 0x0028 && ( n == 0x0005 || n == 0x0200) )) {
532 // seuls (28,5) de vr RET et (28,200) sont des entiers
533 // ... jusqu'a preuve du contraire
536 *(guint32 *) NewValue =
537 SwapLong ((*(guint32 *) NewValue));
540 *(unsigned short *) NewValue =
541 SwapShort ((*(unsigned short *)NewValue));
544 } /* fin if l==2 ==4 */
545 } /* fin if g pair */
548 nouvDcmElem->value = NewValue;
549 // CLEAN ME: simply trash the following line and postpone the process !
550 // SetAsidePixelData(nouvDcmElem);
555 * \ingroup gdcmHeader
556 * \brief If we encountered the offset of the pixels in the file
557 * (Pixel Data) then keep the info aside.
559 void gdcmHeader::SetAsidePixelData(ElValue* elem) {
560 /// FIXME this wall process is bizarre:
561 // on peut pas lire pixel data et pixel location puis
562 // a la fin de la lecture aller interpreter ce qui existe ?
563 // penser a nettoyer les variables globales associes genre
564 // PixelsTrouve ou grPixelTrouve...
566 // They are two cases :
567 // * the pixel data (i.e. the image or the volume) is pointed by it's
568 // default official tag (0x7fe0,0x0010),
569 // * the writer of this file decided to put the image "address" (i.e the
570 // offset from the begining of the file) at a different tag.
571 // Then the "Pixel Data" offset might be found by indirection through
572 // the "Image Location" tag (0x0028, 0x0200). In other terms the Image
573 // Location tag contains the group where the "Pixel Data" offset is and
574 // inside this group the element is conventionally at element 0x0010
575 // (when the norm is respected).
577 // Hence getting our hands on the Pixel Data is a two stage process:
578 // 1/ * find if the "Pixel Data" tag exists.
579 // * if it does not exist, look for the "Pixel Location" tag.
580 // 2/ look at the proper tag ("Pixel Data" or "Pixel Location" when
581 // it exists) what the offset it.
584 g = elem->GetGroup();
585 n = elem->GetElement();
586 if (!grPixelTrouve) { // on n a pas encore trouve les pixels
588 if (n > 0x0200 || g == 0x7FE0 ) { // on a depasse (28,200)
591 grPixelTrouve = true;
593 } else { // on est sur (28,200)
597 char* NewValue = (char*)g_malloc(elem->GetLgrElem()+1);
598 // FIXME: not very elegant conversion
600 *((char*)(&grPixel)+i) = *(NewValue+i);
601 elem->SetValue(NewValue);
603 if (grPixel != 0x7FE0) // Vieux pb Philips
604 numPixel = 0x1010; // encore utile ??
610 } else { // on vient de trouver les pixels
613 PixelPosition = elem->Offset;
614 PixelsTrouves = true;
615 dbg.Verbose(0, "gdcmHeader::SetAsidePixelData:",
622 gdcmDictEntry * gdcmHeader::IsInDicts(guint32 group, guint32 element) {
623 gdcmDictEntry * found = (gdcmDictEntry*)0;
624 if (!RefPubDict && !RefShaDict) {
625 //FIXME build a default dictionary !
626 printf("FIXME in gdcmHeader::IsInDicts\n");
629 found = RefPubDict->GetTag(group, element);
634 found = RefShaDict->GetTag(group, element);
641 string gdcmHeader::GetPubElValByNumber(unsigned short, unsigned short) {
645 * \ingroup gdcmHeader
646 * \brief renvoie un pointeur sur le ID_DCM_HDR correspondant au fichier
647 * @param filename Nom du fichier ACR / LibIDO / DICOM
648 * @return le ID_DCM_HDR
651 void gdcmHeader::BuildHeader(void) {
652 ElValue * newElValue = (ElValue *)0;
655 fseek(fp, 0L, SEEK_END);
657 * obtains the current value of the file-position
658 * indicator for the stream pointed to by stream
660 taille_fich = ftell(fp);
663 while ( (newElValue = ReadNextElement()) ) {
664 PubElVals.Add(newElValue);
669 int gdcmHeader::PrintPubElVal(ostream & os) {
670 return PubElVals.Print(os);
673 void gdcmHeader::PrintPubDict(ostream & os) {
674 RefPubDict->Print(os);