M-INTF(4)
π M-INTF(4) π
okay bestie come SIT with me because this is m-intf β the InterFile 3.3 medical image format for (X)MedCon π©Ίβ¨ β straight out of the Kernel Interfaces Manual, section 4. it's a long one so we're doing this in parts, no skipping π
πΈ DESCRIPTION πΈ
so the file is literally two things at once: the admin data (that's the ASCII part, she's the brains) and the binary image data πΎ. you can shove both into one single file, but honestly? we prefer to split them up into two files like the icons we are. the admin data lives in a file with extension .h33 and the binary data lives in a file with extension .i33. π
this is a wonderful feature, fr fr. π¦ because the header is separate and in ASCII, you can just write your own header for whatever raw image data you've got lying around. I personally use InterFile all the time to turn unsupported formats into a supported format β like, extract the raw image data out of the mystery format and slap an appropriate header on it with everything you know about it. iconic behavior. π
these are the basic defines for the format:
#define MDC_INTF_MAXKEYCHARS 256
char keystr[MDC_INTF_MAXKEYCHARS];
/* the data type */
#define MDC_INTF_STATIC 1
#define MDC_INTF_DYNAMIC 2
#define MDC_INTF_GATED 3
#define MDC_INTF_TOMOGRAPH 4
#define MDC_INTF_CURVE 5
#define MDC_INTF_ROI 6
/* the process status */
#define MDC_INTF_ACQUIRED 1
#define MDC_INTF_RECONSTRUCTED 2
/* gated spect nesting outer level */
#define MDC_INTF_NESTING_SPECT 1
#define MDC_INTF_NESTING_GATED 2
typedef struct MdcInterFile_t {
int data_type, process_status, pixel_type, gspect_nesting;
Uint32 width, height, images_per_dimension, time_slots;
Uint32 data_offset, data_blocks, imagesize, number_images;
Uint32 energy_windows, frame_groups, time_windows, detector_heads;
float pixel_xsize, pixel_ysize, slice_thickness, centre_centre_separation;
float study_duration, image_duration, image_pause, group_pause, ext_rot;
Int8 patient_rot, patient_orient, slice_orient;
double version;
} MDC_INTERFILE;
β¨ what does the format support (and what does it NOT) β¨
vibe check on the format's abilities, bestie π«Ά:
- Color Map β supported: grayscale π«οΈ Β· not supported:
- - File Endian β supported: little or big π Β· not supported:
- - Pixeltypes β supported: 1-bit, all integers, float, double, ASCII β¨ Β· not supported:
- - Scaling factors β quantify & calibrate factors/image are NOT supported, unless you define your own key-value pairs π
- Dimensions/Image β different dimensions for each image are supported π
- Pixeltypes/Image β different pixeltypes for each image are supported, but decoders are not required to be able to read.
(X)MedCondoes support different pixeltypes per image. π
underneath you will find a description of the format. let's goooo π
π 1. ADMINISTRATIVE DATA π
- a) the administrative data are only composed of key-value pairs and exist in the form of ASCII text. the administrative data should be terminated with a
<Ctrl-Z>. - b) all administrative data are to be supplied in the form of key-value pairs in ASCII with delimiters. a
keyis the name of a parameter taking a specificvalue, an example being:
patient name := Elvis Presley <cr><lf>
- c) the maximum permitted number of characters for a key or a value or a comment is 255 characters.
- d) neither keys nor values are to be treated as case sensitive. the characters
<space><tab><underscore><!>may all be treated as white space and ignored. - e) all relevant keys should be included in the intermediate file. a null value is permitted which will invoke the default where specified. the required keys are preceded by an exclamation mark.
- f) a hole line or key-value pairs may have comments appended to them by preceding the comment with a semicolon
<;>. - g) a required key (
"name of data file") is included to point to the image data file, even if the binary data is in the same file of the administrative data. - h) the two alternate keys (
"data starting block") or ("data offset in bytes") are used to indicate the offset of the binary data in the file as specified by the key ("name of data file"). the ("data starting block") represents the offset in number of blocks or 2048 bytes. the use of the key ("data offset in bytes") permits the offset to the binary data to be freely specified.
π¦ 2. IMAGE DATA π¦
- a) the order of the pixel data shall increment by column from left to right, and then by row, from top to bottom. π₯
- b) image data shall be in either bit, signed or unsigned integer format, IEEE floating point format (float or double) or ASCII. when the data pixels are written in ASCII, the text line must not exceed 255 characters.
- c) the default value for the key (
"imagedata byte order") is BIGENDIAN, but LITTLEENDIAN is allowed. the byte order must be respected for integer and floating point numbers. - d) bit data will stored in a single byte, representing 8 pixel values and ordered such that the most significant bit corresponds to the leftmost pixel.
π 3. LIST OF KEYS - VERSION 3.3 (Updated for Gated SPECT) π
okay this is the main character section, buckle up cutie π every key, every default, every comment, all of it:
!INTERFILE:=<NULL>β to indicate that this is an Interfile file!imaging modality:=<ASCII>nucmedβ only nucmed is defined for the purpose of this document!originating system:=<ASCII>β eg.GAMMA-11, MDS, ADAC, etc.!version of keys:=<Numeric>3.3β future versions shall incrementdate of keys:=<DateFormat>1996:09:26β date of version 3.3 in date formatconversion program:=<ASCII>β name of program usedprogram author:=<ASCII>β your chance of fame and fortune β¨program version:=<Numeric>β to keep track of conversion programsprogram date:=<DateFormat>β date of program!GENERAL DATA:=<NULL>β required but can be treated as commentoriginal institution:=<ASCII>β name of hospital etc.contact person:=<ASCII>β another chance of fame (and fortune?) πdata description:=<ASCII>β whatever you want!data starting block:=<Numeric>0β the value is the offset in blocks of 2048 bytes in either the administrative or the data file depending on the key value for name of data file (see below)- OR π
!data offset in bytes:=<Numeric>0β as above but the offset may be specified freely in bytes!name of data file:=<ASCII><NULL>β<NULL>if no image data exists. key is a name of the file where the data are present, either when in a separate binary data file, or when in a combined administrative/binary data filepatient name:=<ASCII>β last name, first name (recommended)!patient ID:=<ASCII>β as used in your hospitalpatient dob:=<DateFormat>β date of birthpatient sex:=<ASCIIlist>Unknown M|F|Unknownβ default is Unknown!!study ID:=<ASCII>β as local conditions dictateexam type:=<ASCII>β description of procedure as abovedata compression:=<ASCII>noneβ name of algorithm if present- e.g. JPEG, etc.data encode:=<ASCII>noneβ name of method of encoding if present- e.g. uuencode etc.organ:=<ASCII>noneβ ENLF: for mapping with DICOM tag (0018,0015) BodyPartExaminedisotope:=<ASCII>|<ASCII>/<ASCII>noneβ ENLF: for mapping with DICOM radionuclide/radiopharmaceutical entries.dose:=<Numeric>0β ENLF: for mapping with DICOM tag (0018,1074) RadionuclideTotalDose [MBq]!GENERAL IMAGE DATA:=<NULL>β again required but treated as comment!type of data:=<ASCIIlist>OtherStatic|Dynamic|Gated|Tomographic|Curve|ROI|GSPECT|Otherβ important - this key is used for many conditionals!total number of images:=<Numeric>β how many images are there altogether in total in the associated data file (for all windows etc.). This overrides any other way of calculating the total number of images.study date:=<DateFormat>β date of the first image included in the data filestudy time:=<TimeFormat>β time for the start of first image specifiedimagedata byte order:=<ASCIIlist>BIGENDIANBIGENDIAN|LITTLEENDIANβ BIGENDIAN is the default if unspecifiedprocess label:=<ASCII>noneβ ENLF: for mapping with DICOM tag (0008,103E) SeriesDescriptionquantification units:=+1.696265e-05β ENLF: global scale factor for mediman dialectNUD/rescale slope:=+1.696265e-05β ENLF: global scale factor for NUD systemsNUD/rescale intercept:=+0.000000e+00β ENLF: global scale intercept for NUD systemsnumber of energy windows:=<Numeric>1β defaulted to one if unspecified
π okay so this next bit loops, bestie. like, actually loops. here's the structure:
for ( number of energy windows, energy window) {
energy window[<energy window>]:=<ASCII>β ASCII text- for example "Tc99m". this starts as"energy window [1]"and then increments toenergy window[2]:= <ASCII>and then on toenergy window[3]:= <ASCII>etc. etc.energy window lower level [<energy window>]:=<Numeric>β value of lower energy level in keV for the corresponding window. starts off as"energy window lower level [1]"and continues[2],[3].. as aboveenergy window upper level [<energy window>]:=<Numeric>β value of upper energy level in keV for the corresponding window. starts off as"energy window upper level [1]"and continues[2],[3].. as aboveflood corrected:=<ASCIIlist>YY|Nβ corrected if unspecifieddecay corrected:=<ASCIIlist>NY|Nβ not corrected if unspecified- and THEN, if
if( type of data = "Static"|"ROI")π:{!STATIC STUDY (General):=<NULL>β label to indicate that this is the static definitionnumber of images/energy window:=<Numeric>1β number of images in THIS energy window- and now ANOTHER loop:
for ( number of images/energy window ){!Static Study (each frame):=<NULL>β included at the beginning of the definition of each new static frame!image number:=<Numeric>β starting from 1 [see above]. must be specified!! starts from 1 and increments though all windows to its maximum value which equals the total number of images in the file!!!matrix size [1]:=<Numeric>β matrix size across (number of columns)- previously x. 32, 64, 128 etc. but not necessarily powers of 2!matrix size [2]:=<Numeric>β matrix size down (number of rows)- previously y. 32, 64, 128 etc. but not necessarily powers of 2!number format:=<ASCIIlist>unsigned integersigned integer|unsigned integer|long float|short float|bit|ASCIIβ as specified!number of bytes per pixel:=<Numeric>β e.g. 1|2|4.. [this key ignored for bit data]
scaling factor (mm/pixel) [1]:= <Numeric>
; size of one pixel going across β used to be called x π
scaling factor (mm/pixel) [2] := <Numeric>
; size of one pixel going down β used to be called y π
image duration (sec) := <Numeric>
; eg. 120.0 β normally a float, and it's per image bestie
image start time := <TimeFormat>
; the time for each image πΈ
label := <ASCII>
; eg Anterior
maximum pixel count := <Numeric>
; for scaling purposes, for each image
total counts := <Numeric>
; either an integer or a float, for each image
}
; End of the frame loop β repeat for every subsequent frame π
}
; End of the static definitions β¨
okay so that's static studies wrapped up, no notes, ate. Now the dynamic ones β and lowkey the pattern is the same every time, it's just bigger, bestie. If you vibed with the static block you're gonna be fine here.
if( type of data = "Dynamic")
{
!DYNAMIC STUDY (general) := <NULL>
; a lil label that says hey, this is a dynamic study π
!number of frame groups := <Numeric> 1
; defaults to 1
for( number of frame groups, frame group number)
{
!Dynamic Study (each frame group) := <NULL>
; repeated for each group of frames β it's the
; "a new group starts here" marker π
!frame group number := <Numeric>
; numbering starts from 1 β and this one MUST be
; specified, no skipping π
!matrix size [1] := <Numeric>
; matrix size across (that's the number of columns)
; β used to be matrix size x
; 32, 64, 128 etc. but not necessarily powers of 2
!matrix size [2] := <Numeric>
; matrix size down (number of rows) β used to be y
; 32, 64, 128 etc. but not necessarily powers of 2
!number format := <ASCIIlist> unsigned integer
signed integer|unsigned integer|
long float|short float|bit|ASCII
; as specified
!number of bytes per pixel := <Numeric>
; e.g. 1|2|4.. (this key is ignored for bit data, fyi)
scaling factor (mm/pixel) [1]:= <Numeric>
; size of pixel across β previously x
scaling factor (mm/pixel) [2] := <Numeric>
; size of pixel down β previously y
!number of images this frame group := <Numeric>
; for each frame group
; (and for each energy window, so yeah, each one)
!image duration (sec) := <Numeric>
; eg 0.2, for each frame group
; (for each energy window)
pause between images (sec) := <Numeric> 0.0
; eg 0.0, default is 0.0
pause between frame groups (sec) := <Numeric> 0.0
; eg 5.0, default 0.0 β it's the time between the
; last frame group (or the start of the study) and
; this frame group β³
!maximum pixel count in group := <Numeric>
; eg 1234 (for scaling purposes)
; this is the max pixel for ALL the frames in this
; group AND this window!! π€
}
; Repeat for each subsequent frame group π
}
; End of dynamic definitions
GATED STUDIES TIME π«Ά Same energy, but now we're doing time windows on top of everything else. So it's a loop inside a block, inside your heart.
if( type of data = "Gated")
{
!GATED STUDY (general) := <NULL>
; again, just a flag saying this is a gated study πΈ
!matrix size [1] := <Numeric>
; matrix size across (number of columns) β previously x
; 32, 64, 128 etc. but not necessarily powers of 2
!matrix size [2] := <Numeric>
; matrix size down (number of rows) β previously y
; 32, 64, 128 etc. but not necessarily powers of 2
!number format := <ASCIIlist> unsigned integer
signed integer|unsigned integer|
long float|short float|bit|ASCII
; as specified
!number of bytes per pixel := <Numeric>
; e.g. 1|2|4|..> (this key is ignored for bit data)
scaling factor (mm/pixel) [1]:= <Numeric>
; size of pixel across β previously x
scaling factor (mm/pixel) [2] := <Numeric>
; size of pixel down β previously y
study duration (elapsed) sec := <Numeric>
; eg 300 β the total elapsed time for the WHOLE study β±οΈ
number of cardiac cycles (observed) := <Numeric>
; total number of cycles if you know it, for this
; energy window
number of time windows := <Numeric> 1
; defaults to 1 if unspecified β it's how many different
; sets of time intervals you've got
for ( number of time windows, time window number)
{
!Gated Study (each time window) := <NULL>
!time window number := <Numeric>
; starting from 1
!number of images in time window := <Numeric>
; eg 24
!image duration (sec) := <Numeric>
; eg 0.04 β for each frame in THIS time window
framing method := <ASCIIlist> Forward
Forward|Backward|Mixed|Other
; default is forward
time window lower limit (sec) := <Numeric>
; a float is normally expected here, for THIS time window
time window upper limit (sec) := <Numeric>
; float normally expected
% R-R cycles acquired this window := <Numeric>
; if known
number of cardiac cycles (acquired) := <Numeric>
; eg 356, if known
study duration (acquired) sec := <Numeric>
; the total acquisition time duration for
; THIS window only (if it can even be computed!!) as
; opposed to the total acquisition time (when those two
; are different) π«
!maximum pixel count := <Numeric>
; for scaling purposes, for all the images in this
; time window (and energy window) only
R-R histogram := <ASCIIlist> N Y|N
; flag to say that one exists!!
}
; Repeat for each subsequent time window. π
}
; End of gated definitions
And now the big one π Tomographic studies, AKA the reason you're here. This block has nested conditionals, bestie. It's giving main character energy and honestly? Deserved.
if( type of data = "Tomographic")
{
!SPECT STUDY (general) := <NULL>
; a flag that says tomographic data β as such it has no
; effect, it's just the vibe check at the door β¨
number of detector heads := <Numeric> 1
; default = 1 if unspecified
for ( number of detector heads )
{
!number of images/energy window := <Numeric>
; the total number of images (for ALL heads) for
; THIS energy window!! πΈ
!process status := Reconstructed Acquired|Reconstructed
; used down below in a conditional β this one MUST be
; defined, no leaving it out π
!matrix size [1] := <Numeric>
; matrix size across (number of columns) β previously x
; 32, 64, 128 etc. but not necessarily powers of 2
!matrix size [2] := <Numeric>
; matrix size down (number of rows) β previously y
; 32, 64, 128 etc. but not necessarily powers of 2
!number format := <ASCIIlist> unsigned integer
signed integer|unsigned integer|
long float|short float|bit|ASCII
; as specified
!number of bytes per pixel := <Numeric>
; e.g. 1|2|4|.. (this key is ignored for bit data)
scaling factor (mm/pixel) [1]:= <Numeric>
; size of pixel across β previously x
scaling factor (mm/pixel) [2] := <Numeric>
; size of pixel down β previously y
!number of projections := <Numeric>
; for example β 64
; ok so note this: it IS the actual number of images per
; head per energy window IF the data are acquired, but
; NOT if the data are reconstructed β because then the
; number of images gets specified separately, as the
; number of slices π§
!extent of rotation := <Numeric>
; e.g 180, 360
!time per projection (sec) := <Numeric>
; important for Acquired data
study duration (sec) := <Numeric>
; eg 1280.0 β for acquired data this should be equal to
; the product of the number of projections and the time
; per projection, but it could be different!! π
!maximum pixel count := <Numeric>
; for scaling β in THIS image series (this head and
; this energy window)
patient orientation := <ASCIIlist> head_in
head_in|feet_in|other
patient rotation := <ASCIIlist> supine
prone|supine|other
if( process status = "acquired")
{
!SPECT STUDY (acquired data):= <NULL>
!direction of rotation := <ASCIIlist> CW CW|CCW
; CW = clockwise, CCW = counter clockwise π
start angle := <Numeric>
; 0 is top-dead-centre, in degrees, in the orientation
; as specified above
first projection angle in data set := <Numeric>
; in degrees, expressed with respect to the
; anterior β angles go in the direction you
; specified above, CW or CCW
acquisition mode := <ASCIIlist> stepped
stepped|continuous
Centre_of_rotation := <ASCIIlist> Corrected
Corrected|Single_value|For_every_angle
; default is "Corrected"
; "Corrected" corresponds to a null centre of rotation
; correction, as previously required by Interfile β that
; is, no centre of rotation information is to be
; passed. The key "Single value" indicates the
; conventional definition of the centre of rotation
; offset being a single value specified for all angles,
; specified for each head when you've got a multiple
; head acquisition.
; The key "For_every_angle" indicates that the centre
; of rotation offset will be specified for each head
; and every angle. This is not currently implemented in
; this Interfile definition, but will be introduced in
; V4.
; The mathematical centre of rotation is assumed to be
; in the exact middle of the projection β for example at
; x= 32.5 y=32.5 for a 64x64 image where the count
; starts from 1. Note that the choice of coordinates
; does not matter, the only constraint being the
; assumption that all projections have a length which
; is an even number of pixels. The centre of rotation
; is specified as the offset from that position to the
; perpendicular dropped from a point on the axis of
; rotation onto the head. π
if( Centre_of_rotation = "Single_value" )
{
heads up bestie: we're deep in a conditional right now and there's more of it coming β don't close anything, don't run off. TheCentre_of_rotationblock in particular is one where the details actually matter, so read that comment properly before you write anything. And remember"For_every_angle"is not implemented in this Interfile definition yet β it's slated for V4. Don't come for me, that's just what the spec says π
ok so we're right in the middle of the Single_value branch and I am thriving β let's keep it rolling π
!X_offset:=<Numeric>β x offset for all angles in mm.x_offsetis the x offset between the perpendicular dropped from the centre of rotation and the dead centre of the matrix. The positive direction for the offset is considered to be that of the increasing projection index β e.g. for a projection with pixels of size6mm, which should be centred at32.5, an offset of+6mmindicates that the centre of rotation is at33.5. Note that since offset is specified in mm, the pixel size must be known. no pixel size, no offset, that's just the rules bestie π«ΆY_offset:=<Numeric>β y offset for all angles in mm.y_offsetis the y offset between that perpendicular dropped from the centre of rotation from that point on the axis of rotation where they_offsetis considered to be zero, and the centre of the camera's field of view. Soy_offsetis the RELATIVE shift of the y-axis with respect to some arbitrary position β normally that from the centre of the field of view of the first at the top dead centre position. So for a single head, this value would normally be expected to be equal to zero. β¨Radius:=<Numeric>β radial distance to centre of rotation in mm, for this head. π
} β end of centre of rotation specification.
orbit:=<ASCIIlist> Circularβ the options areCircular|non-circular.preprocessed:=<ASCII>β preprocessing method.
} β end of process status acquired.
π if( process status = "reconstructed" ) β the reconstructed branch, she's giving reconstruction era
!SPECT STUDY (reconstructed data):=<NULL>method of reconstruction:=<ASCII>!number of slices:=<Numeric>β i.e. number of images in this set for this head and this energy window. πnumber of reference frame:=<Numeric> 0β if unspecified, the frame number originally used for defining slice positions;0= default. [Note β not a very useful key] πslice orientation:=<ASCIIlist> Transverseβ options areTransverse|Coronal|Sagittal|Other; default is transverse if unspecified.slice thickness (pixels):=<Numeric> 1β if unspecified,1= default.centre-centre slice separation (pixels):=<Numeric> 1β e.g.1,2,3,4..., as distinct from slice thickness. also the word centre can also be spelt as center, because of course it can πfilter name:=<ASCII>β e.g. Hann, Hamming, Butterworth.filter parameters:=<ASCII>β Nyquist freq etc.z-axis filter:=<ASCII>β method[1,2,1]etc.attenuation correction coefficient/cm:=<Numeric> 0.0β default0means not done if unspecified.method of attenuation correction:=<ASCII> nonescatter corrected:=<ASCIIlist> Nβ options areY|N.method of scatter correction:=<ASCII> noneoblique reconstruction:=<ASCIIlist> Nβ options areY|N.oblique orientation:=<ASCII>β free text. [Note: ACR-NEMA convention preferred]
} β end of reconstructed tomo.
} β End of tomo.
π * if( type of data = "GSPECT" ) β gated SPECT has entered the chat π
psst, the little * marks stay exactly where the original puts them, ok? keeping the receipts.
*!GATED SPECT STUDY (general):=<NULL>β again, a flag to indicate a gated SPECT study. MIXTURE OF GATED & TOMO π!matrix size [1]:=<Numeric>β matrix size across (number of columns) β previously x.32,64,128etc. but not necessarily powers of 2.!matrix size [2]:=<Numeric>β matrix size down (number of rows) β previously y.32,64,128etc. but not necessarily powers of 2.!number format:=<ASCIIlist> unsigned integerβ options aresigned integer|unsigned integer|long float|short float|bit|ASCII; as specified.!number of bytes per pixel:=<Numeric>β e.g.1|2|4|..>[this key ignored for bit data].!Gated SPECT nesting outer level:=SPECT|GatedGatedβ key to indicate order of images: ifSPECTis outer level, the order is, for every angle give each gated image; ifGatedis outer level, then the order is, for every gated time value, the set of tomographic images.scaling factor (mm/pixel) [1]:=<Numeric>β size of pixel across β previously x.scaling factor (mm/pixel) [2]:=<Numeric>β size of pixel down β previously y.study duration (elapsed) sec:=<Numeric>β eg300, total elapsed time for whole study.number of cardiac cycles (observed):=<Numeric>β total number of cycles if known, for this energy window.number of time windows:=<Numeric> 1β defaults to1if unspecified β number of different sets of time intervals. β
π¦ for ( number of time windows, time window number ) β loop de loop, one per window
!Gated Study (each time window):=<NULL>!time window number:=<Numeric>β starting from 1.*!number of images in time window:=<Numeric>β eg24. NOTE that here in gated SPECT it means images/angle.!image duration (sec):=<Numeric>β eg0.04, for each frame in THIS time window.framing method:=<ASCIIlist> Forwardβ options areForward|Backward|Mixed|Other; default is forward.time window lower limit (sec):=<Numeric>β float normally expected, for THIS time window.time window upper limit (sec):=<Numeric>β float normally expected.% R-R cycles acquired this window:=<Numeric>β if known.number of cardiac cycles (acquired):=<Numeric>β eg356, if known.study duration (acquired) sec:=<Numeric>β total acquisition time duration for this window only (if it can be computed!!) as opposed to total acquisition time (when different).!maximum pixel count:=<Numeric>β for scaling purposes for all images in this time window (and energy window) only. π₯R-R histogram:=<ASCIIlist> Nβ options areY|N; flag to indicate that one exists!!
} β repeat for each subsequent time window.
;start of tomographic keys
number of detector heads:=<Numeric> 1β default =1if unspecified.
πΈ for ( number of detector heads ) β a loop per head, multi-head main character energy
!number of images/energy window:=<Numeric>β total number of images (for all heads) for THIS energy window!!!process status:=Acquired|Reconstructedβ used below in conditional β MUST be defined. no default, no skipping, she's mandatory π«Ά!number of projections:=<Numeric>β for example64. note this is the actual number of images per head per energy window if the data are acquired, but NOT if the data are reconstructed, where the number of images is specified separately asnumber of slices.!extent of rotation:=<Numeric>β e.g.180,360.!time per projection (sec):=<Numeric>β important for Acquired data.patient orientation:=<ASCIIlist> head_inβ options arehead_in|feet_in|other.patient rotation:=<ASCIIlist> supineβ options areprone|supine|other.
π if( process status = "acquired" ) β the acquired branch (nested inside the detector-heads loop, iconic of her)
!SPECT STUDY (acquired data):=<NULL>!direction of rotation:=<ASCIIlist> CWβ options areCW|CCW;CW= clockwise,CCW= counter clockwise.start angle:=<Numeric>β0is top-dead-centre, in degrees, in orientation as specified above.first projection angle in data set:=<Numeric>β in degrees expressed with respect to anterior β angles in direction as specifiedCWorCCW.acquisition mode:=<ASCIIlist> steppedβ options arestepped|continuous.Centre_of_rotation:=<ASCIIlist> Correctedβ options areCorrected|Single_value|For_every_angle. default is"Corrected"."Corrected"corresponds to a null centre of rotation correction, as previously required by Interfile β that is, no centre of rotation information is to be passed.- The key
"Single value"indicates the conventional definition of the centre of rotation offset to be a single value specified for all angles, specified for each head given a multiple head acquisition. - The key
"For_every_angle"indicates that the centre of rotation offset will be specified for each head and every angle.
β οΈ heads up: "For_every_angle" is not currently implemented in this Interfile definition, but it will be introduced in V4. not yet, bestie, not yet π
- The mathematical centre of rotation is assumed to be in the exact middle of the projection β for example at
x= 32.5 y=32.5for a64x64image where the count starts from 1. Note that the choice of coordinates does not matter, the only constraint being the assumption that all projections have a length which is an even number of pixels. The centre of rotation is specified as the offset from that position to the perpendicular dropped from a point on the axis of rotation onto the head.
π«§ if( Centre_of_rotation = "Single_value" ) β and here we are again, full circle, love that for us
!X_offset:=<Numeric>β x offset for all angles in mm.x_offsetis the x offset between the perpendicular dropped from the centre of rotation and the dead centre of the matrix. The positive direction for the offset is considered to be that of the increasing projection index β e.g. for a projection with pixels of size6mm, which should be centred at32.5, an offset of+6mmindicates that the centre of rotation is at33.5. Note that since offset is specified in mm, the pixel size must be known.
okay so we're still deep in the Interfile keyword dictionary β the centre-of-rotation block keeps going and then it wraps itself up like this:
Y_offset := <Numeric>
;y offset for all angles in mm
;y_offset is the y offset between that perpendicular
;dropped from the centre of rotation from that point
;on the axis of rotation where the y_offset is
;considered to be zero, and the centre of the camera's
;field of view. Thus y_offset is the RELATIVE shift of
;the y-axis with respect to some arbitrary position,
;normally that from the centre of the filed of view of
;the first at the top dead centre position. Thus for
;a single head, this value would normally be expected
;to be equal to zero.
Radius := <Numeric>
;radial distance to centre of rotation in mm,
;for this head.
}
;end of centre of rotation specification
orbit := <ASCIIlist> Circular Circular|non-circular
preprocessed := <ASCII>
;preprocessing method
}
; end of process status acquired
so real talk bestie: Y_offset is the y offset for all angles, in mm, and the comment is out here doing a whole thesis about it π β it's the y offset between that perpendicular dropped from the centre of rotation from that point on the axis of rotation where the y_offset counts as zero, and the centre of the camera's field of view. So it's the RELATIVE shift of the y-axis compared to some arbitrary position β normally the one from the centre of the field of view of the first at top dead centre. For a single head you'd normally expect this to be zero. Then Radius is the radial distance to the centre of rotation in mm, for this head. And boom, } β end of centre of rotation specification. β¨
Next: orbit takes an <ASCIIlist> of Circular|non-circular, preprocessed takes <ASCII> and is the preprocessing method, and then } closes it all out β ; end of process status acquired. Iconic, she's tidy.
Then we hit the reconstructed branch, and it is long:
if( process status = "reconstructed")
{
!SPECT STUDY (reconstructed data) := <NULL>
method of reconstruction := <ASCII>
!number of slices := <Numeric>
;i.e. number of images in this set for this
;head and this energy window
number of reference frame := <Numeric> 0
;if unspecified the frame number
;originally used for defining slice positions
;0=default [Note- not a very useful key]
slice orientation := <ASCIIlist> Transverse
Transverse|Coronal|Sagittal|Other
;default is transverse if unspecified
slice thickness (pixels) := <Numeric> 1
;if unspecified 1=default
centre-centre slice separation (pixels):=<Numeric> 1
;e.g.1,2,3,4... as distinct from slice thickness
;the word centre can also be spelt as center
filter name := <ASCII>
;e.g. Hann, Hamming, Butterworth
filter parameters := <ASCII>
;Nyquist freq etc.
z-axis filter := <ASCII>
;method [1,2,1] etc.
attenuation correction coefficient/cm := <Numeric> 0.0
;default 0 means not done if unspecified
method of attenuation correction := <ASCII> none
scatter corrected := <ASCIIlist> N Y|N
method of scatter correction := <ASCII> none
oblique reconstruction := <ASCIIlist> N Y|N
oblique orientation := <ASCII>
;free text [Note ACR-NEMA convention preferred]
}
;end of reconstructed tomo
}
;End of GATED SPECT
}
;end of loop for energy windows
let me walk you through it because she deserves it π
!SPECT STUDY (reconstructed data)is a<NULL>label, just flagging this as the reconstructed SPECT study.method of reconstructionβ<ASCII>, that's it, that's the tea.!number of slicesis<Numeric>: literally how many images are in this set for this head and this energy window.number of reference frameis<Numeric>with a default of0β if you leave it unspecified it's the frame number originally used for defining slice positions. the manpage itself says[Note- not a very useful key]ππ keepin' it real.slice orientationis an<ASCIIlist>ofTransverse|Coronal|Sagittal|Other, and the default is transverse if unspecified.slice thickness (pixels)is<Numeric>, default1if unspecified.centre-centre slice separation (pixels):=<Numeric>defaults to1, values like1,2,3,4..., and it's as distinct from slice thickness β also the word centre can be spelt center, so don't come for either spelling, both slay.filter nameis<ASCII>β e.g.Hann,Hamming,Butterworth.filter parametersis<ASCII>β e.g. Nyquist freq etc.z-axis filteris<ASCII>β method[1,2,1]etc.attenuation correction coefficient/cmis<Numeric>, default0.0, and default0means not done if unspecified.method of attenuation correctionis<ASCII>defaulting tonone.scatter correctedis<ASCIIlist>Y|N, withmethod of scatter correctionbeing<ASCII>defaulting tonone.oblique reconstructionis<ASCIIlist>Y|N, andoblique orientationis<ASCII>β free text, and the note says[Note ACR-NEMA convention preferred].
then } wraps the reconstructed tomo, } closes GATED SPECT, and } ends the loop for energy windows. architecture ππ«Ά
π₯ if it's a Curve π₯
now for curve data, which the manpage has STRONG opinions about:
if( type of data = "Curve")
{
!CURVE DATA := <NULL>
;label to indicate that this is the curve definition
;curves should always be kept in separate data files
;and not together with the administrative data
Curve_dimensions := <Numeric> 2
;how many dimensions- ONLY 2 is permitted in V3.3.
;Even if a single vector of values is required
;both matrix size[1] and [2] must be defined
;although one of them should take the value 1.
;A set of x,y values is 2 dimensional with normally
;matrix size[1] or matrix size[2] equal to 2.
;and the other matrix size specifying
;the number of PAIRS of values present (see Fig 1).
;Matrix sizes greater than 2 for BOTH dimensions are not
;recommended.
!matrix size[1] := <Numeric>
;matrix size across (number of columns)
!matrix size[2] := <Numeric>
;matrix size down (number of rows)
!number format := <ASCIIlist> unsigned integer
signed integer|unsigned integer|
long float|short float|bit|ASCII
!number of bytes per pixel := <Numeric>
;e.g. 1|2|4|.. [this key ignored for bit data]
Type_of_curve := <ASCII>
;what kind of curve is it, for example time activity curve
;ROI indicates that this is a list
;of vectors corresponding to an ROI.
for( Curve_dimensions, dimension)
{
;When matrix size[1] or matrix size[2] equals 2 (the normal
;case) such that the data comprise pairs of values,
;then Label[1], Units[1] etc. refers to the set of first
;values for each pair, and Label[2] etc. refers the set of
;second values for each pair.
Label[<dimension>] := <ASCII>
;a text label for the corresponding axis e.g. "counts"
Units[<dimension>] := <ASCII>
;units of measurement for the corresponding axis
;e.g. "units[1]:=counts/sec"
Min[<dimension>] := <Numeric>
;Minimum of set of values as indicated in units as defined,
;optional
Max[<dimension>] := <Numeric>
;Maximum value as above, optional
}
;End of loop for curve dimensions
}
!END OF INTERFILE := <NULL>
breaking it down, no cap:
!CURVE DATAis a<NULL>label marking that this is the curve definition. And listen β curves should always be kept in separate data files and not together with the administrative data. that's the rule, bestie, respect it πCurve_dimensionsis<Numeric>with a value of2. how many dimensions β ONLY 2 is permitted in V3.3. even if you just want a single vector of values, bothmatrix size[1]and[2]must be defined, though one of them should take the value1. a set of x,y values is 2 dimensional with normallymatrix size[1]ormatrix size[2]equal to2, and the other matrix size specifying the number of PAIRS of values present (see Fig 1). and heads up: matrix sizes greater than 2 for BOTH dimensions are not recommended π!matrix size[1]is<Numeric>β matrix size across (number of columns).!matrix size[2]is<Numeric>β matrix size down (number of rows).!number formatis an<ASCIIlist>ofsigned integer|unsigned integer|long float|short float|bit|ASCII.!number of bytes per pixelis<Numeric>β e.g.1|2|4|..and note that this key is ignored for bit data.Type_of_curveis<ASCII>β what kind of curve is it, for example a time activity curve.ROIindicates that this is a list of vectors corresponding to an ROI.- then there's a
for( Curve_dimensions, dimension)loop. whenmatrix size[1]ormatrix size[2]equals2(the normal case) so the data is pairs of values, thenLabel[1],Units[1]etc. refer to the set of first values for each pair, andLabel[2]etc. refer to the set of second values for each pair. π§Label[<dimension>]is<ASCII>β a text label for the corresponding axis, e.g."counts".Units[<dimension>]is<ASCII>β units of measurement for the corresponding axis, e.g."units[1]:=counts/sec".Min[<dimension>]is<Numeric>β minimum of the set of values, as indicated in units as defined, optional.Max[<dimension>]is<Numeric>β maximum value as above, optional.
}ends the loop for curve dimensions.- and finally, the mic drop:
!END OF INTERFILE := <NULL>β that's a wrap on the whole file, periodt. π
π FILES π
/usr/local/xmedcon/source/m-intf.hβ the header file./usr/local/xmedcon/source/m-intf.cβ the source file.
π SEE ALSO π
medcon(1),xmedcon(1),xmedcon-config(1)m-acr(4),m-anlz(4),m-gif(5),m-inw(4),m-ecat(4)medcon(3)
π AUTHOR π
the (X)MedCon project was originally written by Erik Nolf (eNlf) for the former PET-Centre at Ghent University (Belgium). give him his flowers π
- e-mail:
enlf-at-users.sourceforge.net - www:
http://xmedcon.sourceforge.net
and that's the entire page, bestie β every last key, every last } πΈ this one lives in section M-INTF(4) of the manual (that's the footer stamp you'd see at the bottom of the page, source/version and date and all), so if you ever forget which manpage this was, it's this one. go forth and parse Interfile files like the main character you are π
π¦
π¦ where this page comes from
- debian trixie: libmdc-dev 0.25.1-gtk3+dfsg-1 Β· GPL-2.0-or-later
- ubuntu questing: libmdc-dev 0.25.1-gtk3+dfsg-1
- ubuntu resolute: libmdc-dev 0.25.1-gtk3+dfsg-1build1 Β· GPL-2.0-or-later
- ubuntu stonking: libmdc-dev 0.26.1-gtk3+dfsg-1 Β· GPL-2.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms