13
$\begingroup$

Bug partially fixed in 12.1 [CASE:4081592]
ImageResolution now works correctly for Image, but not for Image3D.


Does Mathematica have a feature to set the pixel aspect ratio of 2D images?

For example,

Image3DSlices[ExampleData[{"TestImage3D", "CThead"}], 100, 3]

Mathematica graphics

I can resize this to the correct aspect ratio, like so:

Mathematica graphics

But I do not want to modify the data. I just want to control how it is displayed. With 3D images, we can use BoxRatios. What about 2D?

$\endgroup$
6
  • $\begingroup$ Something like Graphics[Raster[ImageData[img, DataReversed -> True]], AspectRatio -> 1]? $\endgroup$
    – chuy
    Jun 21, 2018 at 18:51
  • $\begingroup$ You can use Show with the AspectRatio->1, but the image quality is much lower. Or something like this: ImageResize[yourimage, {#, #} &@Max@ImageDimensions[yourimage]]. I work with images quite often but I don't know an option like BoxRatios that work with image objects. $\endgroup$
    – user45937
    Jun 21, 2018 at 18:54
  • 1
    $\begingroup$ From Filling a rectangle with an image: VertexTextureCoordinates looks like it could work. e.g. Graphics[{Texture[img], Polygon[{{0, 0}, {0, y}, {x, y}, {x, 0}}, VertexTextureCoordinates -> {{0, 0}, {0, 1}, {1, 1}, {1, 0}}]}] But it is quite verbose. I'm pretty sure there is an elegant way to convert an Image into a Graphics object. $\endgroup$ Jun 21, 2018 at 23:02
  • $\begingroup$ @JungHwanMin Pre 11.0 or something Rasterize returned a Graphics object. You could see what format it used and try that. That could then of course take a direct AspectRatio, but I can’t try this right now. $\endgroup$
    – b3m2a1
    Jun 22, 2018 at 2:32
  • $\begingroup$ @b3m2a1 Head[Rasterize[img]] returns Image, so it's no good. $\endgroup$ Jun 22, 2018 at 5:34

4 Answers 4

5
$\begingroup$

Alexey suggested that the following undocumented (but not going away) ImageSize syntax could be used:

img = Image3DSlices[ExampleData[{"TestImage3D","CThead"}],100,3];
Show[img, ImageSize -> 1 -> {1,2}]

enter image description here

I think this does what you want?

$\endgroup$
2
  • $\begingroup$ Instead of Show, we could also use Image. One issue is that when resizing with the mouse, the aspect ratio jumps back to how it was. Where did Alexey show this syntax? $\endgroup$
    – Szabolcs
    Oct 7, 2018 at 19:03
  • $\begingroup$ @Szabolcs In a comment to my answer to a similar question, Alexey suggested that the same method might be useful for this question. $\endgroup$
    – Carl Woll
    Oct 7, 2018 at 19:06
6
$\begingroup$

UPDATE

In version 12.1 they implemented correct handling of the ImageResolution option for Image (but for Image3D ImageResolution is still ignored).


Original answer

Note: I've sent a suggestion for improvement to the tech support with a link to this post ([CASE:4081592]).

Mathematica already basically supports different resolutions in different directions for Image when importing and exporting (what is documented, for example, on the Documentation page for "JPEG" format):

Options[img = Import["ExampleData/coneflower.jpg", 
   ImageResolution -> {72, 144}], ImageResolution]

Options[Import@Export["coneflower_new.jpg", img], ImageResolution]
{ImageResolution -> {72, 144}}

{ImageResolution -> {72, 144}}

So halirutan is right: the developers just overlooked that this should also influence how the image is rendered by the FrontEnd. Other picture viewers respect this setting, for example here is how Windows Photo Viewer renders the image "coneflower_new.jpg":

screenshot

As one can see from the screenshot, the image is scaled up two times in horizontal direction according to the specified ImageResolution which is two times smaller in horizontal direction (72 dpi) than in vertical direction (144 dpi), as expected.

But I think that it is incorrect to set AspectRatio of the whole image equal to pixel aspect ratio (as halirutan did), because we should also take into account actual ImageDimensions when calculating the overall AspectRatio. So here is my fix for Image which makes it respecting the ImageResolution setting (I wrapped changed portions of the original code by (* edit start *) and (* edit end *) tags):

Unprotect[Image];
MakeBoxes[Image`ImageDump`img : Image[_, Image`ImageDump`type_, Image`ImageDump`info___], 
    Image`ImageDump`fmt_] /; Image`ValidImageQHold[Image`ImageDump`img] ^:= With[{
    Image`ImageDump`newarray = 
     Image`InternalImageData[Image`ImageDump`img, Interleaving -> True], 
    Image`ImageDump`tag = 
     BoxForm`ImageTag[Image`ImageDump`type, 
      Sequence @@ 
       FilterRules[FilterRules[{Image`ImageDump`info}, Except[Options["Graphics"]]], 
        Except[ImageMarkers]]], 
    Image`ImageDump`imgdata = 
     ImageData[Image`ImageDump`img, {"Width", "Height", "DataRange", "ColorFunction"}], 
    Image`ImageDump`imgsize = 
     Image`ImageDump`ruleValue[{Image`ImageDump`info}, ImageSize], 
    Image`ImageDump`mag = 
     Image`ImageDump`ruleValue[{Image`ImageDump`info}, Magnification],
    (* edit start *)
    Image`ImageDump`pixelAspectRatio = 
     With[{Image`ImageDump`res = 
        Image`ImageDump`ruleValue[{Image`ImageDump`info}, ImageResolution]},
      If[MatchQ[Image`ImageDump`res, {_?(# > 0 &), _?(# > 0 &)}],
       Divide @@ Image`ImageDump`res, 1, 1]
      ]
    (* edit end *)
    },
   With[{
     (* edit start *)
     Image`ImageDump`width = 
      Image`ImageDump`imgdata[[1]]/
       If[Image`ImageDump`pixelAspectRatio < 1, Image`ImageDump`pixelAspectRatio, 1, 1],
     Image`ImageDump`height = 
      Image`ImageDump`imgdata[[2]]*
       If[Image`ImageDump`pixelAspectRatio > 1, Image`ImageDump`pixelAspectRatio, 1, 1],
     (* edit end *)
     Image`ImageDump`range = Image`ImageDump`imgdata[[3]],
     Image`ImageDump`cfun = Image`ImageDump`imgdata[[4]]},
    With[{Image`ImageDump`options = Sequence @@ Join[{
          DefaultBaseStyle -> "ImageGraphics",
          ImageSizeRaw -> {Image`ImageDump`width, Image`ImageDump`height},
          PlotRange -> {{0, Image`ImageDump`width}, {0, Image`ImageDump`height}}
          }, 
         Image`ImageDump`ConvertImageSizeToFE[Image`ImageDump`imgsize, 
          Image`ImageDump`mag], 
         FilterRules[{Image`ImageDump`info}, Image`ImageDump`$typesetOptions]]}, 
     GraphicsBox[
      TagBox[RasterBox[
        Image`ImageDump`newarray, {{0, Image`ImageDump`height}, {Image`ImageDump`width, 
          0}}, Image`ImageDump`range, ColorFunction -> Image`ImageDump`cfun], 
       Image`ImageDump`tag, Selectable -> False], Image`ImageDump`options]]]];
Protect[Image];

Now

Import["coneflower_new.jpg"]

screenshot

Image[Image3DSlices[ExampleData[{"TestImage3D", "CThead"}], 100, 3], 
 ImageResolution -> {72, 72/2}]

screenshot

$\endgroup$
4
  • $\begingroup$ Yes, you are absolutely right. I was sloppy in my implementation. The displayed size of the image should take the resolution as an indicator of what aspect ratio one pixel has. I have described it correctly, but my implementation does not reflect this. When you get an answer from Wolfram, would you update Szabolcs question with the bug-head and tag it as bugs? $\endgroup$
    – halirutan
    Jun 26, 2018 at 14:15
  • 1
    $\begingroup$ @halirutan Yes, I'll update the answer (and the question if relevant) with responce from the support when I receive it. $\endgroup$ Jun 26, 2018 at 15:09
  • $\begingroup$ @halirutan I've received a responce: "I have passed along a report to our developers on this suggestion." If something more informative will come, I'll post an update. $\endgroup$ Jun 27, 2018 at 7:31
  • $\begingroup$ @halirutan They fixed this bug for Image in version 12.1 (but not for Image3D). $\endgroup$ Mar 19, 2020 at 12:45
4
$\begingroup$

This might have been overlooked in the implementation of Image. When I see this right, then the correct way to handle this would be to respect the setting of ImageResolution and acknowledge that it can be different for different directions

enter image description here

and it is not unusual for medical devices like a CT to have a drastic difference in resolution for different directions. For a plain image, that doesn't know its resolution, the displayed size is always determined by assuming pixels are squares and using the ImageDimension-ratio as aspect-ratio.

However, I would have expected that something like this works

img = Image3DSlices[ExampleData[{"TestImage3D", "CThead"}], 100, 3];
Image[img, ImageResolution -> {100, 100}]

Mathematica graphics

Unfortunately it doesn't, although the GraphicsBox would support it. But we could fix MakeBoxes for Image and we would get

enter image description here

Below are the changes I made as an image because it seems not easily possible to copy code from the PrintDefinitions view.

enter image description here

$\endgroup$
0
4
$\begingroup$

It is indeed surprisingly difficult. We have to set explicit AspectRatio and ImageSize (and optionally Magnification) after conversion into the corresponding Graphics object:

pixelAspectRatio = 2;
img = Image3DSlices[ExampleData[{"TestImage3D", "CThead"}], 100, 3];

Show[img, 
 AspectRatio -> pixelAspectRatio*ImageAspectRatio[img], 
 ImageSize -> ImageDimensions[img]*{1, pixelAspectRatio}
 (*, BaseStyle -> Magnification -> 1*)]

Other options:

Show[img, PlotRange -> All, PlotRangePadding -> None,
  ImageSize -> ImageDimensions[img]*{1, pixelAspectRatio}
  (*, BaseStyle -> Magnification -> 1*)] /. 
 r_Raster :> Scale[r, {1, pixelAspectRatio}]

With[{is = ImageDimensions[img]*{1, pixelAspectRatio}}, 
 Show[img, PlotRange -> All, PlotRangePadding -> None, ImageSize -> is
   (*, BaseStyle -> Magnification -> 1*)] /. 
  Raster[array_, _, opts___] :> Raster[array, {{0, 0}, is}, opts]]

We can avoid conversion into a Graphics object by using Style for changing the default options of GraphicsBox locally. Unfortunately via GraphicsBoxOptions we can't change ImageSize stored in the Image object itself, hence we should change it directly via Image:

Style[Image[img, ImageSize -> ImageDimensions[img]*{1, pixelAspectRatio}],
 GraphicsBoxOptions ->
  {AspectRatio -> pixelAspectRatio*ImageAspectRatio[img] 
   (*, BaseStyle -> Magnification -> 1*)}]

ImageSize -> All also works:

Style[Image[img, ImageSize -> All], GraphicsBoxOptions ->
  {AspectRatio -> pixelAspectRatio*ImageAspectRatio[img]
   (*, BaseStyle -> Magnification -> 1*)}]

We can prove that the displayed size is indeed correct by rasterizing the output and taking its "RasterSize":

ImageDimensions[img]
Rasterize[PreviousCell[], "RasterSize"]
{256, 99}

{256, 198}

screenshot

$\endgroup$
1
  • $\begingroup$ +1 for the info that Style can be used. I looked through the complete MakeBoxes of Image to see if I can somehow inject this option so that you don't have either fix the code or use a wrapper like Style. Seems not possible with Image alone. $\endgroup$
    – halirutan
    Jun 22, 2018 at 22:27

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.