M-ACR(4)

πŸ“¦
version 0.26.1-gtk3 Β· 4 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

πŸŽ€ M-ACR(4) πŸŽ€


πŸ’– NAME πŸ’–

m-acr β€” the ACR/NEMA medical image format, as handled by (X)MedCon ✨


🩺 DESCRIPTION 🩺

okay so bestie, real talk: we are absolutely lost in standards, versions and ACR/NEMA dialects 😭 Here you can only read how we handle this format. That's it. That's the deal.

The format lives in one single file, with the extension .ima πŸ“

The format is made up of a group of fields with different elements, sitting in a series of tags. Does that explain literally anything to you? Yeah, us neither πŸ™ˆ

The image data is stored from left to right and from top to bottom πŸ‘‘

✨ the basic defines for the format ✨

#define MDC_ACR_TAG_SIZE 8    /* size of group+element+length */

typedef struct {
  Uint16 group;           /* the kind of group   */
  Uint16 element;         /* the kind of element */
  Uint32  length;         /* the length of data  */
  Uint8   *data;          /* pointer to the data */
} MDC_ACR_TAG;

πŸ’… what does the format support, and what does it NOT support πŸ’…

  • Color Map β€” grayscale βœ… β€” literally nothing else, no
  • File Endian β€” little & big βœ… β€” both, iconically
  • Pixeltypes β€” all integers (signed/unsigned) βœ… β€” but float & double? ❌ not supported, sorry
  • Scaling factors β€” quantify & calibrate factors/image are NOT supported, unless you go ahead and define your own tags 🫢
  • Dimensions/Image β€” different dimensions for each image are supported 🌈
  • Pixeltypes/Image β€” different pixeltypes for each image are supported 🌈

πŸ¦‹ an ACR/NEMA file could look like this πŸ¦‹

in fact, it is the kind we write πŸ’–

πŸ‘‘ GROUP 0x0008 β€” Identifying information πŸ‘‘

  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0000 (the first element of any group); Uint32 Element length in bytes: (4); Int32 Length of group in bytes: (X) (143) β€” where X = [total length of this group] - [total bytes of this first tag (12)]
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0001; Uint32 Element length in bytes: (4); Int32 Total bytes to end of file: Y β€” where Y = [filesize] - [total bytes of first two tags]
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0010; Uint32 Element length in bytes: (12); char * Recognition Code: (ACR-NEMA 2.0)
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0020; Uint32 Element length in bytes: (10); char * Study Date: yyyy.mm.dd
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0030; Uint32 Element length in bytes: (14); char * Study Time: hh.mm.ss.frac_
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0040; Uint32 Element length in bytes: (2); Int16 Data Set Type: 0 = Images, 256 = Raw data
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0060; Uint32 Element length in bytes: (2); char * Image Modality: (NM)
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0070; Uint32 Element length in bytes: (24); char * Manufacturer: ((X)MedCon)
  • Uint16 Group number: 0x0008; Uint16 Element number: 0x0080; Uint32 Element length in bytes: (11); char * Institution ID: (NucMed)

🧚 GROUP 0x0010 β€” Patient Information 🧚

  • Uint16 Group number: 0x0010; Uint16 Element number: 0x0000; Uint32 Element length in bytes: (4); Int32 Length of group in bytes: (96)
  • Uint16 Group number: 0x0010; Uint16 Element number: 0x0010; Uint32 Element length in bytes: (35); char * Patient Name:
  • Uint16 Group number: 0x0010; Uint16 Element number: 0x0020; Uint32 Element length in bytes: (35); char * Patient ID:
  • Uint16 Group number: 0x0010; Uint16 Element number: 0x0040; Uint32 Element length in bytes: (2); char * Patient Sex: M_ = male, F_ = female, O_ = others

🌸 GROUP 0x0018 β€” Acquisition Information 🌸

  • Uint16 Group number: 0x0018; Uint16 Element number: 0x0000; Uint32 Element length in bytes: (4); Int32 Length of group in bytes: (122)
  • Uint16 Group number: 0x0018; Uint16 Element number: 0x0030; Uint32 Element length in bytes: 32; char * Radionuclide:
  • Uint16 Group number: 0x0018; Uint16 Element number: 0x0050; Uint32 Element length in bytes: (13); char * Slice Thickness in mm: (+0.000000e+00)
  • Uint16 Group number: 0x0018; Uint16 Element number: 0x0088; Uint32 Element length in bytes: (13); char * Slice Spacing in mm: (+0.000000e+00)
  • Uint16 Group number: 0x0018; Uint16 Element number: 0x1120; Uint32 Element length in bytes: (13); float Gantry Tilt in degrees: (+0.000000e+00)
  • Uint16 Group number: 0x0018; Uint16 Element number: 0x1160; Uint32 Element length in bytes: (32); char * Filter Type:
  • Uint16 Group number: 0x0018; Uint16 Element number: 0x5100; Uint32 Element length in bytes: (32); char * Patient Position: (supine)
    • supine = face-up on the table
    • prone = face-down towards the table
    • other? 🫠

πŸ“ GROUP 0x0020 β€” Relationship Information πŸ“

  • Uint16 Group number: 0x0020; Uint16 Element number: 0x0000; Uint32 Element length in bytes: (4); Int32 Length of group in bytes: (352)
  • Uint16 Group number: 0x0020; Uint16 Element number: 0x0010; Uint32 Element length in bytes: (10); char * Study ID:
  • Uint16 Group number: 0x0020; Uint16 Element number: 0x0013; Uint32 Element length in bytes: (6); char * Image Number:
  • Uint16 Group number: 0x0020; Uint16 Element number: 0x0020; Uint32 Element length in bytes: (32); char * Patient Orientation: (L\P)

(that's direction of image row in patient \ direction of image column in patient, iykyk)

  • 'L' = Left (hand)
  • 'A' = Anterior (to front)
  • 'H' = Head
  • 'R' = Right (hand)
  • 'P' = Posterior (to back)
  • 'F' = Feet
  • Uint16 Group number: 0x0020; Uint16 Element number: 0x0030; Uint32 Element length in bytes: (41); char * Image Position in mm:
                        * * * * * * * * * * * * *

This gives the 3D equipment based coordinates of the upper left hand corner in the image πŸ”₯ Example: (+0.000000e+00\+0.000000e+00\+0.000000e+00) where the first is the =X-axis, second is =Y-axis, third is =Z-axis.

"When facing the front of the gantry (equipment device), and with the gantry in a neutral (untilted) position, the x-axis is increasing to the right; the y-axis is increasing down (gravitational attraction); and the z-axis is defined as the line orthogonal to x and y, with increasing values from the front to the back of the gantry."

(From a Papyrus 2.3 document: UIN/HCUG 1990, 91)

My note: where is its origin?? πŸ‘€ For an ECAT 931 scanner we choose the origin in the right/back/down point of the gantry:

        A
     ______H
    |\______\F
  R |.|.... |  L
     \|_____|
        P

Looking to the scanner, this is a representation of the volume our scanner detects. My origin is in the point we can't see ;-) Our images are transversal slices, beginning at the head towards the feet (so patient orientation = L\P) and the patient position is supine πŸ’–

Therefore, the coordinates of the first pixel in our images is:

Image 0:
 -(PIXEL_X_SIZE*PIXELS_IN_X);-(PIXEL_Y_SIZE*PIXELS_IN_Y);-0
Image 1:
 -(PIXEL_X_SIZE*PIXELS_IN_X);-(PIXEL_Y_SIZE*PIXELS_IN_Y);-(SLICE_WIDTH*1)
Image n:
 -(PIXEL_X_SIZE*PIXELS_IN_X);-(PIXEL_Y_SIZE*PIXELS_IN_Y);-(SLICE_WIDTH*N)
                     |                           |
                image width                 image height

A view of the coordinate system you can see in 0x0020;0x0035 πŸ‘‘

okay so not to alarm you but... it could all be wrong too 😭 that's just the inherent vibe of reverse-engineered medical imaging lore. and btw, for DICOM this whole thing is retired stuff β€” legacy, a relic, we keep it for the history books.

                          * * * * * * * * * * * * *
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0020
  Uint16  Element number                : 0x0032
  Uint32  Element length  in bytes      : (41)
  char *  Image Position (Patient) in mm:
                          * * * * * * * * * * * * *

Same energy as the thing above, except now we're anchoring it to the patient's coordinate system 🫢 And here's the DICOM glow-up replacement for those values:

"The direction of the axes is defined fully by the patient's orientation.
The x-axis is increasing to the left hand side of the patient. The y-axis
is increasing to the posterior side of the patient. The z-axis is increasing
toward the head of the patient.

The patient based coordinate system is a right handed system, i.e. the vector
cross product of a unit vector along the positive x-axis and a unit vector
along the positive y-axis is equal to a unit vector along the positive z-axis.

NOTE: If a patient lies parallel to the ground, face-up on the table, with
      his feet-to-head direction the same as front-to-back direction of the
      imaging equipment, the direction of the axes of the patient based
      coordinate system and equipment based coordinate system in previous
      versions of the DICOM Standard will coincide"

(From the NEMA Standards Publication PS3.3(199X)


                          * * * * * * * * * * * * *
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0020
  Uint16  Element number                : 0x0032
  Uint32  Element length  in bytes      : (83)
  char *  Image Orientation             :

                          * * * * * * * * * * * * *

So based on 0x0020;0x0030, these are the direction cosines of a unit vector on the first row and on the first column, based on the equipment coordinate system (or our patient coordinate system, because as we already said, they coincide πŸ’«):

                         (to back of the scanner)
                      +Z (or head of the patient)
                      \
                       \  coordinate system
                        \
                         \_ _ _ _ _ _ _ +X (to right of the scanner)
                          |                 (or left  of the patient)
                          |
                          |
                          |
                         +Y (to the ground)
                            (or back of the patient)

                          * * * * * * * * * * * * *

Remember: we take transversal slices (Right to Left of patient, Anterior to Posterior) while the patient is supine with head first in gantry. Then the images live in the plane XY and the unit vectors are:

     upper left
     corner of image         (X)
                     + - - - - > (x1,y1,z1 = 1,0,0)
                     |               unit vector on row
                     |
                     |
                (Y)  V (x2,y2,z2 = 0,1,0)
                           unit vector on column

In this case:

                a) in point (x1,y1,z1)
                     X direction cosinus = +1
                     Y direction cosinus = -0
                     Z direction cosinus = -0

                b) in point (x2,y2,z2)
                     X direction cosinus = +0
                     Y direction cosinus = +1
                     Z direction cosinus = +0

So how do the signs and values actually work πŸ‘€ easy:

                       cos(0 or 360) = +1
                       cos(90)       = +0
                       cos(180)      = -1
                       cos(270)      = -0

To get the angle between an axis and the vector you use a so called corkscrew-rule πŸŒ€: you turn from THE AXIS towards THE VECTOR (= that's the angle), same direction, so that the corkscrew proceeds in the direction of an axis orthogonal on the plane formed by THE AXIS and THE VECTOR. Well... that's what it should be I think πŸ˜… if your images are tilted it gets a bit harder, doesn't it?

For the above ECAT 931 acquisition an example value should be:

       (+1.000000e+00\-0.000000e+00\+0.000000e+00\
        +0.000000e+00\+1.000000e+00\-0.000000e+00)

Again, this tag is retired for DICOM...


                          * * * * * * * * * * * * *
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0020
  Uint16  Element number                : 0x0037
  Uint32  Element length  in bytes      : (83)
  char *  Image Orientation Patient     :

The same as for tag 0x0020;0x0032, except now it's considered for the patient coordinate system...

  ===========================================================================
  GROUP 0x0028        Image Presentation
  ===========================================================================
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0000
  Uint32  Element length  in bytes      : (4)
  Int32   Length of group in bytes      : (127)
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0005
  Uint32  Element length  in bytes      : (2)
  Int16   Image Dimensions              : (2)
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0010
  Uint32  Element length  in bytes      : (2)
  Int16   Rows                          :
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0011
  Uint32  Element length  in bytes      : (2)
  Int16   Columns                       :
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0030
  Uint32  Element length  in bytes      : (27)
  char *  Pixel Size      in mm         : (+0.000000e+00\+0.000000e+00)
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0060
  Uint32  Element length  in bytes      : (4)
  char *  Compression code              : (NONE)
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0100
  Uint32  Element length  in bytes      : (2)
  Int16   Bits Allocated                :
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0101
  Uint32  Element length  in bytes      : (2)
  Int16   Bits per Pixel                :
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0102
  Uint32  Element length  in bytes      : (2)
  Int16   High Bit                      :
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0103
  Uint32  Element length  in bytes      : (2)
  Int16   Pixel Representation          : 0 = unsigned
                                          1 = signed
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x0028
  Uint16  Element number                : 0x0200
  Uint32  Element length  in bytes      : (2)
  Int16   Image Location                : (7fe0)
  ===========================================================================
  GROUP 0x7fe0        Pixel Information
  ===========================================================================
  Uint16  Group   number                : 0x7fe0
  Uint16  Element number                : 0x0000
  Uint32  Element length  in bytes      : (4)
  Int32   Length of group in bytes      : Z
  Z = [imagesize] + 8
  ---------------------------------------------------------------------------
  Uint16  Group   number                : 0x7fe0
  Uint16  Element number                : 0x0010
  Uint32  Element length  in bytes      : (imagesize)
  Uint8 * Image Data                    :
  ---------------------------------------------------------------------------

That was an example of an ACR/NEMA file holding one image, as normal ACR/NEMA files do πŸ™ˆ But because we're interested in multiple images, we use an ACR/NEMA dialect such as Papyrus. And in that case we sequentially concatenate different ACR/NEMA files into one single large file! iconic behaviour honestly πŸ’…


✨ NOTES ✨

Because of that previous remark, we gotta flag this: in the Element 0x0001 of Group 0x0008, the [filesize] means the filesize in case of this one ACR/NEMA file, and NOT the real filesize! 😭

  • For the Group 0x0028, Element 0x0100: Bits Allocated β€” we only support a multiple of 8.
  • For the Group 0x0028, Element 0x0102: High Bit β€” we only support High Bit = [Bits per Pixel] - 1, so we only accept images stored in the file endian type.

πŸ“ FILES πŸ“

  • /usr/local/xmedcon/source/m-acr.h β€” The header file.
  • /usr/local/xmedcon/source/m-acr.c β€” The source file.

πŸ‘€ SEE ALSO πŸ‘€


πŸ’– AUTHOR πŸ’–

(X)MedCon project was originally written by Erik Nolf (eNlf) for the former PET-Centre at Ghent University (Belgium). πŸ‘‘


and that's the whole page bestie, no cap 🌸 the source file for this one is m-acr.h / m-acr.c and it's filed under M-ACR(4) β€” you made it all the way to the footer, so go drink some water and stretch, you absolute legend πŸ«Άβœ¨πŸ’…

πŸ“¦ where this page comes from