Xmri uses the MRI machine coordinate system supplied by the GE MRI machine as the fundamental reference.
We need to be able to relate everything going on in the program to this: When a 2-D orthogonal slice is shown on the screen, we need know and display what MRI coordinate it was taken at, and if a pixel on that slice is mouse-clicked, we need to be able to compute and display the corresponding MRI coordinates.
These coordinates also establish the mappings we need between different datasets: When someone clicks on the 3D computer reconstruction photo, and we want to display the corresponding point on the original 2-D raw dataset, we use the MRI coordinate space maintained independently on each dataset to make the bridge.
The MRI coordinate system axes are:
We display these images on an SGI, and wish to do overlay graphics on the images using graphics primitives expressed in the MRI coordinate space.
Unfortunately, the SGI routines expect the coordinate system to increase to the right along the X axis, and increase upward along the Y axis: Attempting to specify the reverse simply leads to an inversion of the graphics being displayed. (?) As a fix, I'm negating the machine coordinates for internal use, and re-negating before exporting or displaying them.
Other coordinate systems which need to be specified concern the layout of the voxels in the original MRI datafiles -- which voxels correspond to which MRI coordinates? -- and the layout of the pixels in the image files recieved from avs -- which pixels correspond to which of the given MRI coordinates?
Skandha4 follows the CommonLisp standard for array layout in memory: Consecutive memory elements of a multidimensional array are stored in row-major order, addressed as:
(send ary :aref 0 0) (send ary :aref 0 1) (send ary :aref 0 2) ... (send ary :aref 1 0) (send ary :aref 1 1) (send ary :aref 1 2) ...
The SGI lrectwrite() fn which we use to copy images from memory to the screen uses a coordinate system in which x,y are zero at lower-left on the monitor, x increasing to the right and y increasing upward.
Consecutive memory locations in the given image are taken to be a left-to-right pixel row.
(Consecutive rows in memory are apparently interpreted as filling the given image square bottom-to-top; We only write one row at a time using lrectwrite, hence are not dependent on this.)
The Skandha4 pixel-drawing commands also draw consecutive array elements left-to-right on the screen, and successive array rows bottom-to-top.
This means that for a 256-square image:
(send pix :aref 0 0) ; is bottom-left on screen, 1-st in ram (send pix :aref 0 255) ; is bottom-right on screen, 256-th in ram (send pix :aref 255 0) ; is top-left on screen, 65280-th in ram (send pix :aref 255 255) ; is top-right on screen, 65526-th in ram
The Skandha4 XVOL-GET-VOXEL-SLICE function extracts a slice from a 3-D voxel dataset along one of three axes, resulting in a displayable image. The three mappings are:
Axis 0: Copies (volume k * *) into (slice * *) for some k, maintaining memory order. (I.e., copies one contiguous sequence of values.) Axis 1: Copies (volume * j *) into (slice * *) for some j, maintaining memory order. (I.e., copies one row per contiguous plane in the voxel array.) Axis 2: Copies (volume * * i) into (slice * *) for some i, maintaining memory order. (I.e., copies one column per contiguous plane in the voxel array.)
The Skandha4 XVOL-LOAD-RANGE-IMAGE-FROM-FILE function loads an rgbz field from an AVS field.
"dim1" in the header is taken to be the width-in-pixels.
"dim2" in the header is taken to be the height-in-pixels.
The file contents are taken to be red-green-blue-depth quadruples, one byte per component, arranged in 'height' rows of 'width' entries. Red, green and blue entries range from 0 for black to 255 for maximum intensity. The depth entry has a range of...?
Successive quadruples within a row are taken to be of increasing X.
Successive rows within the file are taken to be of DECREASING Y.
Six binary floating-point values appended to the file are taken to be (in file order, with typical values given):
min_x 67.2375 x-range: 150 max_x -83.8891 min_y 78.7078 y-range: 99 max_y -43.7156 min_z 56.3969 z-range: 60 max_z 5.73125
When loaded and displayed as described, this results in a brain with:
Anterior to left (x-axis), Superior at top (y-axis) Left side near (z-axis)
The X results match expectation from xmri-load-X-dataset info below: Most positive MRI coord matches front of head.
The Y results conflict with expectation from xmri-load-X-dataset info below: Most positive should be top of head. Evidently the Y convention has gotten reversed, probably due to conflicting conventions as to whether first row in an image file should be at top or bottom of screen. To compensate for this, we interchange min_y and max_y during the loading process (see below).
I can't directly check the Z results, since I can't by eye distinguish left and right sides of the brain in available data, but our success in matching operative photos suggests we have it right, hence assuming info has been passed through by AVS, correctly, most positive R axis coords are left side.
These should represent the bounding box information for the displayed image, in MRI machine coordinates.
At present these are massaged and then attached to the pixel relation under the following names:
:min-x - min_x :max-x - max_x :min-y max_y :max-y min_y :min-z - min_z :max-z - max_z
This scrambling of values is unpleasant, but reconciles the following needs:
The above mapping accomplishes that, at the cost that we must remember to negate X and Z but not Y values before displaying or saving them.
(:min-z is taken to be the "near" surface of the bounding box, and :max-z the "far" surface.)
Loyd tells me that the depth values are scaled not 0->255, but 0->N-1 where N is the number of voxels which he has clipped the volume to. Empirically, the depth values are smaller for nearer parts of the brain, and larger for more distant parts, with the background where no brain is present being set to zero.
xmri-read-voxel-slice function reads
one slice from a GE MRI file into a
voxel array. Currently this function always
stores into one contiguous plane of the
(array k * *), with succeeding
values in the file becoming succeeding values in
the array, and succeeding rows within the file
becoming PRECEDING rows within the voxel
k==0 gives the first plane in
the voxel array.
This function loads one or more datasets from GE MRI files, rotates and resamples them to a common scale and orientation, and returns the resulting matched voxel arrays.
In the standard resulting orientation,
Axis 0 (slices) is Anterior; (Voxel 0 ~ -80 Voxel N ~ 80) Axis 1 (rows) is Superior; (Voxel 0 ~ -80 Voxel N ~ 80) Axis 2 (columns) is Right(/left). (Voxel 0 ~ 80 Voxel N ~ -80)
In 3-D display mode on the raw data:
"Coronal" slider samples on Axis 0; "Axial" slider samples on Axis 1; "Sagital" slider samples on Axis 2;
Also, on: "Coronal"/Axis 0/A, Slice 0/-80 is back of head, Slice N/ 80 is front "Axial" /Axis 1/S, Slice 0/-80 is neck, Slice N/ 80 is top; "Sagital"/Axis 2/R, Slice 0/ 80 is (left), Slice N/-80 is (right) ("(left)" and "(right)" parenthesized because this appears to be the relationship, but I cannot verify it directly by eye in Skandha4 from raw data.)