6
$\begingroup$

I am writing some functions in Mathematica for representing MRI anatomical image volumes. The natural way to represent such a volume seems to be as an Image3D form. However, I've noticed an inconsistency in the way that these images are rendered and was wondering if there is a way around this.

Some MRI volumes display just fine because they have intensity values that have been scaled to be in the range of 0-1. Many MRI volumes, however, have values ranging from 0-256 or higher. I want to returning an Image3D object in which the data has not been modified, so I would like to keep the values between 0 and 256 in this case. Unfortunately, these volumes don't display correctly, even with a correctly specified color function.

I can't reproduce the MRI data due to subject confidentiality, but this example shows the problem:

volumeData = {{{0, 0, 0}, {0, 0.5, 0}, {0, 0, 0}},
              {{0, 0.5, 0}, {0.5, 1, 0.5}, {0, 0.5, 0}},
              {{0, 0, 0}, {0, 0.5, 0}, {0, 0, 0}}};
colorFunction = Function[
  Blend[
    {{0., RGBColor[0., 0., 0., 0.]},
     {0.1, RGBColor[0.2, 0.2, 0.2, 0.]}, 
     {0.15, RGBColor[0.5, 0.5, 0.5, 0.8]}, 
     {1.0, RGBColor[0.9, 0.9, 0.9, 1.]}}, 
    #]];
GraphicsRow[
  {Image3D[
     volumeData,
     ColorFunction -> colorFunction,
     Boxed -> True],
   Image3D[
     256.0 * volumeData,
     ColorFunction -> (colorFunction[#/256.0]&),
     Boxed -> True]}]

This simple example produces the following output: graphical output for the above code fragment

Does anyone know how to fix this so that data in ranges outside of 0 - 1 can be displayed in an Image3D object?

$\endgroup$
4
  • $\begingroup$ I notice that if you change the final colorFunction argument from #/256.0 to simply # you get a gray image. $\endgroup$ Jan 23, 2015 at 17:18
  • $\begingroup$ This is because values over 1.0, according to my color function, are assigned RGBColor[0.9,0.9,0.9,1.0]. $\endgroup$
    – nben
    Jan 23, 2015 at 17:31
  • $\begingroup$ Welcome to Mathematica.SE! I suggest the following: 1) As you receive help, try to give it too, by answering questions in your area of expertise. 2) Read the faq! 3) When you see good questions and answers, vote them up by clicking the gray triangles, because the credibility of the system is based on the reputation gained by users sharing their knowledge. Also, please remember to accept the answer, if any, that solves your problem, by clicking the checkmark sign! $\endgroup$
    – user9660
    Feb 22, 2015 at 3:47
  • 1
    $\begingroup$ Image3D simply doesn't have this feature. It uses the standard range 0..1, and it's not meant for data storage. For your application it's likely better to keep the data in an array and think of Image3D as simply a visualization function. You can write your own function which Rescales the data to make it suitable for Image3D. $\endgroup$
    – Szabolcs
    Mar 24, 2015 at 21:42

2 Answers 2

3
$\begingroup$

Data type

enter image description here

You wrote:

Many MRI volumes, however, have values ranging from 0-256 or higher. I want to returning an Image3D object in which the data has not been modified, so I would like to keep the values between 0 and 256 in this case.

If integer values [0, 255] are acceptable you can specify the "Byte" data type for Image3D:

Image3D[255*volumeData, "Byte", Boxed -> True, ColorFunction -> colorFunction]

enter image description here

Scaling

By using ColorFunction -> Print and observing the Messages window one can see that values are already rescaled to [0, 1]:

Image3D[5000 * volumeData, Boxed -> True, ColorFunction -> Print];
0.
0.00392157
0.00784314
. . .
0.992157
0.996078
1.

The coloring is the same regardless of the magnitude of the scaling factor above one:

Table[
 Image3D[s*volumeData, Boxed -> True, ColorFunction -> "Rainbow"],
 {s, {2, 30, 160, 5000, 400000}}
]

enter image description here

However this is not the same as for the base volumeData natively over [0, 1]:

Image3D[volumeData, Boxed -> True, ColorFunction -> "Rainbow"]

enter image description here

(The choice of "Rainbow" is not the best for Image3D but it makes clear the difference in this case.)

Why this is I don't yet know, and the rescaling used is not described that I can find. It seems that ColorFunctionScaling is not a valid Option for Image3D so one cannot simply override this scaling either.

The simplest solution would of course be to rescale your data manually (using Rescale) but you rejected this alternative out of hand.

Another option is to apply colorFunction to the data itself, but this of course also changes the data. Since you did not explain why you do not wish to change the data it is hard to know how to help.

Image3D[Map[colorFunction[#/256] &, 256.0 volumeData, {3}], Boxed -> True]

enter image description here

$\endgroup$
11
  • $\begingroup$ The raw values of the MRI volumes have meanings for researchers, e.g., white-matter segmentation often relies on values being near 110, a value which has been determined empirically, so yes, rescaling the data is out of hand. $\endgroup$
    – nben
    Jan 23, 2015 at 17:59
  • $\begingroup$ @user21382 I added a new section at the top of my answer. Perhaps it is what you need. Is the data really over [0, 256] or is it standard 8-bit data [0, 255]? $\endgroup$
    – Mr.Wizard
    Jan 23, 2015 at 18:03
  • $\begingroup$ They are not actually bytes, and many volumes have values that go beyond 256. I believe that what you're observing in your initial answer is that when values go above 1 they get colored red while values that remain 0 regardless of the scaling factor remain purple. The green in the bottom panel is due to those cells having a value of 0.5 without scaling. Try, for example, changing all of the 0's in volumeData to 0.01. I believe that the Image3D function is asking the color function for values between 0 and 1 then interpolates the colors based on these. $\endgroup$
    – nben
    Jan 23, 2015 at 18:18
  • $\begingroup$ @user21382 (1) Okay. (2) I think you're right regarding scaling. Now, are all MRI values integers in [0, 65535]? $\endgroup$
    – Mr.Wizard
    Jan 23, 2015 at 18:34
  • 1
    $\begingroup$ The data has been determined empirically and has units that are standard in the field and which should not change between representations. The specific numeric quantities in the data are important to researchers who use the data. The Image3D object supports useful operations like dilation and segmentation, but changing the scale of the data will break existing algorithms and muddy interpretations of the volume. Additionally, volumes obtained with different scanning protocols will have different maxima and should not be scaled to comparable numbers. And yes, I expect users to extract the data. $\endgroup$
    – nben
    Jan 23, 2015 at 19:06
0
$\begingroup$

You could use ListDensityPlot3D for visualization purposes instead. This also has the useful option "OpacityFunction"...

IMO, you could also use Image3D and rescale the data only for display purposes. You can work with Image3D's with colors that are out-of-0-1-range just fine, they just don't display correctly.

(*Data*)
normalizedVolumeData = {{{0, 0, 0}, {0, 0.5, 0}, {0, 0, 0}}, {{0, 0.5,
      0}, {0.5, 1, 0.5}, {0, 0.5, 0}}, {{0, 0, 0}, {0, 0.5, 0}, {0, 0,
      0}}};

mriFactor = 256.;
mriVolumeData = normalizedVolumeData*mriFactor;

(*Display...*)
normalizedColorFunction = 
  Function[Blend[{{0., RGBColor[0., 0., 0., 0.]}, {0.1, 
      RGBColor[0.2, 0.2, 0.2, 0.]}, {0.15, 
      RGBColor[0.5, 0.5, 0.5, 0.8]}, {1.0, 
      RGBColor[0.9, 0.9, 0.9, 1.]}}, #]];


mriColorFunction = normalizedColorFunction@(#/mriFactor) &;

(*Opacity Function only available in ListDensityPlot3D!*)
opt = {ColorFunctionScaling -> False, 
   ColorFunction -> normalizedColorFunction, 
   OpacityFunctionScaling -> False, OpacityFunction -> Identity};
optMri = {ColorFunctionScaling -> False, 
   ColorFunction -> mriColorFunction, OpacityFunctionScaling -> False,
    OpacityFunction -> Identity};

"Using ListDensityPlot3D"
GraphicsRow[{
  ListDensityPlot3D[normalizedVolumeData, Sequence @@ opt],
  ListDensityPlot3D[mriVolumeData, Sequence @@ optMri]
  }]

(*with Image3D (no OpacityFunction!)*)
normalizedImage = 
  Image3D[normalizedVolumeData, 
   ColorFunction -> normalizedColorFunction];
mriImage = Image3D[mriVolumeData, ColorFunction -> mriColorFunction];


mriDisplay[mriImage_Image3D] := 
  Image3D[ImageData[mriImage]/mriFactor, 
   ColorFunction -> normalizedColorFunction];

"Using Image3D without correction"
Row[{normalizedImage, 
  Framed@Labeled[mriImage, 
    "this does not display correctly, but carries the right data:"]}]
ImageData@mriImage

"Using Image3D and correction"
Row[{normalizedImage, mriDisplay@mriImage}]

Finally

If you don't like the shading-like effect ListDensityPlot3D gives, here's a hackfix:

disableShadingLDP[i_Graphics3D] := 
  Graphics3D[
   First@i /. (Method -> 
       x_) -> (Method -> {"InterpolateValues" -> False, 
        "FastRendering" -> True, "SampleLayers" -> Automatic})];

Row[{ListDensityPlot3D[mriVolumeData, Sequence @@ optMri], 
  disableShadingLDP@
   ListDensityPlot3D[mriVolumeData, Sequence @@ optMri]}]

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.